算法

数据输入>1000000时使用scanf,否则使用cin

排序

排序算法 平均时间复杂度
冒泡排序 O(n2)
选择排序 O(n2)
插入排序 O(n2)
希尔排序 O(n1.5)
快速排序 O(N*logN)
归并排序 O(N*logN)
堆排序 O(N*logN)
基数排序 O(d(n+r))

快速排序——分治

  1. 确定分界点
  2. 调整区间
  3. 递归处理左右两端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n;
int q[N];

void quick_sort(int q[], int l, int r)
{
if(l >= r) return;
//int x = [q(l + r +1) / 2], i = l - 1, j = r + 1;
int x = q[l + r >> 1], i = l - 1, j = r + 1; // 注意是向上取整,因为向下取整可能使得x取到q[l]
while(i < j)
{
do i ++ ; while (q[i] < x); // 不能取等号,因为若数据都相等则无限循环触发数组越界
do j -- ; while (q[j] > x); // do while 保证i,j一定更新
if (i < j) swap(q[i], q[j]); //可以使用if(i <= j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
//quick_sort(q, l, i - 1), quick_sort(q, i, r);
}
//关于quick_sort()取i,j的问题,若为i则取右边界,若为j则取左边界


int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

quick_sort(q, 0, n-1);

for (int i = 0; i < n; i ++ ) printf("%d ", q[i]);

return 0;
}

C++ sort()其实是小范围插入排序,大范围快速排序

归并排序——分治

  1. 确定分界点:二分之一
  2. 递归排序left,right
  3. 归并——合二为一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

#include <iostream>

using namespace std;

const int N = 1e6 + 10;
int n;
int q[N],tmp[N];

void merge_sort(int q[], int l, int r)
{
if (l >= r) return; //结束递归返回

int mid = l + r >> 1; //通过mid调整边界

merge_sort(q, l, mid), merge_sort(q, mid + 1 , r); //递归,先比较最小的分组

int k = 0, i = l, j = mid + 1;
while(i <= mid && j <= r)
if (q[i] <= q[j]) tmp[ k ++ ] = q[ i ++ ]; //比较两部分
else tmp[ k ++ ] = q [ j ++ ];
while (i <= mid) tmp[ k ++ ] = q[ i ++ ]; //将剩余值写入,
while (j <= r) tmp[ k ++ ] = q[ j ++ ]; · //由于前面的循环语句,这里最多只有一个while进行多次循环
for (i = l, j = 0; i <= r; i ++, j ++) q[i] = tmp[j]; //写回数组
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++) scanf("%d", &q[i]);

merge_sort(q, 0 , n-1);

for (int i = 0; i < n; i++) printf("%d ", q[i]);

return 0;

}

二分

整数二分

  1. mid = (l+r+1)/2
  • true [mid, r ] l = mid

  • false [l, mid - 1] r = mid -1

  1. mid = (1+r)/2
  • true [l, mid] r = mid

  • false [mid+1, r] l = mid +1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int q[N];


int main()
{
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i ++) scanf("%d", & q[i]);
while( m -- )
{
int x;
scanf("%d", &x);

int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r >> 1; //左半二分,确定左边界
if(q[mid] >= x) r = mid;
else l = mid + 1;
}
if( q[l] != x) cout << "-1 -1" << endl;
else
{
cout << l << ' ';
int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r +1 >> 1; //右半二分,确定右边界
if(q[mid] <= x) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}

浮点数二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

int main()
{
double x;
cin >> x;
double l = -10000, r = 10000; //l和r表示n的范围
while (r - l > 1e-8) //一般精度比题目+2次方
{
double mid = (l + r) / 2; //二分
if( mid * mid * mid >= x) r = mid;
else l = mid;
}

printf("%lf\n",l);

return 0;

}

高精度

A+B、A-B、A*b、A➗b

Java中,可以使用BigInteger类来表示任意精度的整数,以及BigDecimal类来表示任意精度的浮点数。

Python中,可以直接使用内置的int类型来表示任意精度的整数。Python的整数类型可以自动扩展以适应大数值,不会发生溢出。

vector

向量(Vector)是一个封装了动态大小数组的顺序容器(Sequence Container)。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。

1.顺序序列

顺序容器中的元素按照严格的线性顺序排序。可以通过元素在序列中的位置访问对应的元素。

2.动态数组

支持对序列中的任意元素进行快速直接访问,甚至可以通过指针算述进行该操作。提供了在序列末尾相对快速地添加/删除元素的操作。

3.能够感知内存分配器的(Allocator-aware)

容器使用一个内存分配器对象来动态地处理它的存储需求。

C++ vector 容器浅析 | 菜鸟教程 (runoob.com)

A+B

  1. 大整数存储,数组起始位置存放最低位置
  2. 按照数学加法原理进行计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <vector>

using namespace std;

vector<int> add(vector<int> &A, vector<int> &B)
{
vector<int> C;
int t = 0;
if (A.size() < B.size()) return add(B, A);
for (int i = 0; i < A.size() || i < B.size(); i ++ )
{
//t + = A[i];
if (i < A.size()) t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10; //去进位
}
if (t) C.push_back(1); //有无进位判断
return C;

}


int main()
{
string a,b;
vector<int> A, B;
cin >> a >> b ;
for (int i = a.size() - 1; i >= 0; i --) A.push_back(a[i] - '0');
//push_back在数组的最后添加一个数据,类似于栈
for (int i = b.size() - 1; i >= 0; i --) B.push_back(b[i] - '0');

auto C = add(A, B);

for (int i = C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);

return 0;

}

A-B

  1. 大整数存储,数组起始位置存放最低位置

  2. 判断A,B大小,大数减小数,加符号

  3. Ai - Bi 借位

  4. 若为符号,转化为绝对值相加减

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <vector>

using namespace std;

bool cmp(vector<int> &A, vector<int> &B)
{
if(A.size() != B.size()) return A.size() > B.size();
for (int i = A.size() - 1; i >= 0; i -- )
if (A[i] != B[i])
return A[i] > B[i];

return true;
}


vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;


for (int i = 0 ,t = 0; i < A.size(); i++)
{
t = A[i] - t; //消除进位
if( i < B.size()) t -= B[i]; //运算
C.push_back((t + 10) % 10); //求绝对值
if ( t < 0) t = 1; //产生进位
else
t = 0;
}


while (C.size() > 1 && C.back() == 0) C.pop_back(); //去除多余的0

return C;

}


int main()
{
string a,b;
vector<int> A, B;
cin >> a >> b ;
for (int i = a.size() - 1; i >= 0; i --) A.push_back(a[i] - '0');
//push_back在数组的最后添加一个数据,类似于栈
for (int i = b.size() - 1; i >= 0; i --) B.push_back(b[i] - '0');

if (cmp(A, B)) //大减小
{
auto C = sub(A, B);
for (int i = C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);
}
else
{
auto C = sub(B, A);
printf("-");
for (int i = C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);
}
return 0;

}

A*b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <vector>

using namespace std;

vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back(); //去除0

return C;
}

int main()
{
string a;
int b;
cin >> a >> b;
if (b == 0) {printf("0"); return 0;} //0特解

vector<int> A;

for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');

auto C = mul(A, b);

for (int i = C.size() - 1; i >= 0; i --) printf("%d",C[i]);

return 0;
}

A➗b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<int> div(vector<int> &A, int b, int &r)
{
vector<int> C;
r = 0;

for (int i = A.size() - 1; i >= 0 ; i -- )
{
r = r * 10 + A[i];
C.push_back(r/b);
r %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back(); //去除0

return C;
}

int main()
{
string a;
int b;
cin >> a >> b;

vector<int> A;

for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
int r;
auto C = div(A, b, r);

for (int i = C.size() - 1; i >= 0; i --) printf("%d",C[i]);
cout << endl << r <<endl;
return 0;
}

前缀和与差分

前缀和

一维前缀
  • 一次运算计算出所有区间的和,空间换时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

using namespace std;

const int N = 100010;
int n, m;
int a[N], s[N];


int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);

for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] +a[i];

while( m-- )
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n",s[r]- s[l -1 ]);
}
return 0;

}
二维前缀
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>

using namespace std;

const int N = 1010;
int n, m, q;
int a[N][N], s[N][N];


int main()
{


scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &a[i][j]);

for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] = s[i - 1][j] + s[i][ j - 1] - s[i - 1][j - 1] + a[i][j];
//计算前缀和

while( q-- )
{
int x1, x2, y1, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n",s[x2][y2] - s[x1 -1][y2] - s[x2][y1 - 1] + s[x1 -1][y1 - 1]);
//计算子矩阵的和
}
return 0;

}

差分

  • 前缀和的逆运算
  • 数组b[i]即原数组a[1]+…..+a[i]
  • 用O(1)的时间给原数组某个区间内的值全部加上C

a[0]= 0;

b[1] = a[1] - a[0];

b[2] = a[2] - a[1];

b[3] =a [3] - a[2];

……..

b[n] = a[n] - a[n-1];

一维差分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int a[N], b[N]; //a[N]原矩阵,b[N]差分矩阵

void insert(int l, int r, int c)
{
b[l] += c;
b[r+1] -= c;
}

int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);

for (int i = 1; i <= n; i ++) insert(i, i ,a[i]);

while( m -- )
{
int l, r , c;
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c);
}
for (int i = 1; i <= n; i++) b[i] += b[i - 1];

for (int i = 1; i <= n; i ++) printf("%d ",b[i]);

return 0;


}

二维差分

AcWing 798. 差分矩阵 【 c++详细题解 】 - AcWing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>


using namespace std;

const int N = 1010;
int n, m ,q;
int a[N][N], b[N][N];


void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}



int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &a[i][j]);

for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
insert(i, j, i ,j ,a[i][j]);

while(q -- )
{
int x1, x2, y1, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >>c;
insert(x1, y1, x2 ,y2 ,c);
}

for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
b[i][j] += b[i - 1][j] + b[i][j - 1] -b[i - 1][j - 1]; //逆运算

for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= m; j ++ ) printf("%d", b[i][j]);
puts("");




return 0;
}

双指针算法

  • 将O(n**2)优化到O(n)

for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;

}

for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

const int N = 100010;
int a[N], s[N]; //s[N]统计字母出现次数
//j指向不重复序列头,i指向不重复序列尾
int main()
{
int n, r = 0;
cin >> n;
for (int i = 0, j = 0; i < n; i++)
{
cin >> a[i];
++ s[a[i]];
while (s[a[i]] > 1) -- s[a[j++]]; //不能用if,因为当检测到两个相同字母时,要j++直到消除先添加的字母。
r = max(r, i - j + 1);
}
cout << r;
return 0;
}

位运算

lowbit(x)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

int lowbit(int x)
{
return x & -x; //10010 & (01101+1)
}

int main()
{
int n;
cin >> n;
while(n --)
{
int x;
cin >> x;
int res = 0;
while(x) x-= lowbit(x), res++;

cout << res << ' ';
}
return 0;
}

离散化

值域跨度大,但是很稀疏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

typedef pair<int, int> PII;

const int N = 300010;

int n, m;
int a[N], s[N];


vector<int> alls;
vector<PII> add, query;


int find(int x) //二分查找
{
int l = 0, r = alls.size() - 1;
while(l < r)
{
int mid = l + r >> 1;
if(alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
}

int main()
{
cin >> n >> m;
for (int i = 0;i < n; i ++)
{
int x, c;
cin >> x >> c;
add.push_back({x,c});
alls.push_back(x);
}

for (int i = 0;i < m; i ++)
{
int l, r;
cin >> l >> r;
query.push_back({l, r}); //存储查找键值
alls.push_back(l);
alls.push_back(r);
}

//去重,值可以累加,但是坐标只有一个
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin() ,alls.end()), alls.end()) ;

for (auto item : add) //迭代器遍历
{
int x = find(item.first);
a[x] += item.second;
}

for (int i = 1 ; i <= alls.size(); i ++) s[i] = s[i - 1] + a[i]; // 区间和

for (auto item : query)
{
int l = find(item.first), r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}

return 0;
}

区间合并

  • 按区间左端点排序