模板 - 普及/提高−难度模板
普及/提高−
B3611 【模板】传递闭包
判断一个点是否能到达另一个点。
因为 $n\leq100$,故用 Floyed 直接是否计算路径。
1 | |
P3371 【模板】单源最短路径(弱化版)(SPFA)
给定一个 $n$ 个点,$m$ 条有向边的带非负权图,请你计算从 $s$ 出发,到每个点的距离。
1 | |
P4779 【模板】单源最短路径(标准版)(dijkstra堆优化)
给定一个 $n$ 个点,$m$ 条有向边的带非负权图,请你计算从 $s$ 出发,到每个点的距离。
1 | |
P3385 【模板】负环
给定一个 $n$ 个点的有向图,求出图中是否存在从顶点 $1$ 出发能到达的负环。
负环的定义是:一条边权之和为负数的回路。
1 | |
P3374 【模板】树状数组1(单点修改,区间查询)
操作 $1$
格式:1 x k
含义:将第 $x$ 个数加上 $k$操作 $2$:
格式:2 x y
含义:输出区间 $[x,y]$ 内每个数的和树状数组做法
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#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m;
int tree[N];
int lowbit(int x) {
return x & (-x);
}
void add(int i, int x) {
while (i <= n) {
tree[i] += x;
i += lowbit(i);
}
return;
}
int sum(int x) {
int ans = 0;
while (x) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
int a;
scanf("%d", &a);
add(i, a);
}
for (int i = 1; i <= m; ++i) {
int op, a, b;
scanf("%d%d%d", &op, &a, &b);
if (op == 1) {
add(a, b);
}
if (op == 2) {
printf("%d\n", sum(b) - sum(a - 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
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
70
71
72
73#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m;
int a[N];
struct Node {
int l, r;
int sum;
} tree[N * 4];
void build(int i, int l, int r) {
tree[i].l = l, tree[i].r = r;
if (l == r) {
tree[i].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(i << 1, l, mid);
build(i << 1 | 1, mid + 1, r);
tree[i].sum = tree[i << 1].sum + tree[i << 1 | 1].sum;
return;
}
int query(int i, int l, int r) {
if (l <= tree[i].l && r >= tree[i].r) {
return tree[i].sum;
}
int s = 0;
int mid = (tree[i].l + tree[i].r) >> 1;
if (l <= mid) {
s += query(i << 1, l, r);
}
if (r > mid) {
s += query(i << 1 | 1, l, r);
}
return s;
}
void add(int i, int add_i, int add_sum) {
if (tree[i].l == tree[i].r) {
tree[i].sum += add_sum;
return;
}
int mid = (tree[i].l + tree[i].r) >> 1;
if (add_i <= mid) {
add(i << 1, add_i, add_sum);
} else {
add(i << 1 | 1, add_i, add_sum);
}
tree[i].sum = tree[i << 1].sum + tree[i << 1 | 1].sum;
return;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
build(1, 1, n);
for (int i = 1; i <= m; ++i) {
int op, a, b;
scanf("%d%d%d", &op, &a, &b);
if (op == 1) {
add(1, a, b);
} else {
printf("%d\n", query(1, a, b));
}
}
return 0;
}
P3368 【模板】树状数组2(区间修改,单点查询)
操作 $1$
格式:1 x y k
含义:将区间 $[x,y]$ 内每个数加上 $k$操作 $2$:
格式:2 x
含义:输出第 $x$ 个数的值树状数组做法
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#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m;
int a[N], tree[N];
int lowbit(int x) {
return x & -x;
}
void add(int i, int x) {
while (i <= n) {
tree[i] += x;
i += lowbit(i);
}
return ;
}
int sum(int x) {
int ans = 0;
while (x) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= m; ++i) {
int op, x;
scanf("%d", &op);
if (op == 1) {
int y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, z);
add(y + 1, -z);
} else if (op == 2) {
scanf("%d", &x);
printf("%d\n", a[x] + sum(x));
}
}
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m;
int a[N];
struct Node {
int l, r;
int sum, add;
} tree[N * 4];
void pushdown(int i) {
if (tree[i].add) {
tree[2 * i].add += tree[i].add;
tree[2 * i + 1].add += tree[i].add;
tree[2 * i].sum += tree[i].add * (tree[2 * i].r - tree[2 * i].l + 1);
tree[2 * i + 1].sum += tree[i].add * (tree[2 * i + 1].r - tree[2 * i + 1].l + 1);
tree[i].add = 0;
}
}
void build(int i, int l, int r) {
tree[i].l = l, tree[i].r = r;
if (l == r) {
tree[i].sum = a[l];
return;
}
int mid = (l + r) / 2;
build(i << 1, l, mid);
build(i << 1 | 1, mid + 1, r);
tree[i].sum = tree[i << 1].sum + tree[i << 1 | 1].sum;
return;
}
void update_add(int i, int add_l, int add_r, int add_sum) {
if (tree[i].l >= add_l && tree[i].r <= add_r) {
tree[i].sum += add_sum * (tree[i].r - tree[i].l + 1);
tree[i].add += add_sum;
return ;
}
pushdown(i);
int mid = (tree[i].l + tree[i].r) / 2;
if (add_l <= mid) {
update_add(i << 1, add_l, add_r, add_sum);
}
if (add_r > mid) {
update_add(i << 1 | 1, add_l, add_r, add_sum);
}
tree[i].sum = tree[i << 1].sum + tree[i << 1 | 1].sum;
return;
}
int query(int i, int query_i) {
if (tree[i].l == tree[i].r) {
return tree[i].sum;
}
pushdown(i);
int mid = (tree[i].l + tree[i].r) / 2;
if (query_i <= mid) {
return query(i << 1, query_i);
} else {
return query(i << 1 | 1, query_i);
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
build(1, 1, n);
for (int i = 1; i <= m; ++i) {
int op, a;
scanf("%d", &op);
if (op == 1) {
int b, c;
scanf("%d%d%d", &a, &b, &c);
update_add(1, a, b, c);
} else {
scanf("%d", &a);
cout << query(1, a) << endl;
}
}
return 0;
}
P3366 【模板】最小生成树
$kruskal$ 算法
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#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 5;
const int M = 2e5 + 5;
int n, m;
int fa[N];
int ltt, ans;
struct node {
int from, to, val;
} edge[M];
bool cmp(node x, node y) {
return x.val < y.val;
}
int find(int x) {
if (fa[x] == x) {
return x;
}
return fa[x] = find(fa[x]);
}
void kruskal() {
sort(edge, edge + m, cmp);
for (int i = 1; i <= n; ++i) {
fa[i] = i;
}
for (int i = 1; i <= m; ++i) {
int u = edge[i].from, v = edge[i].to;
u = find(u), v = find(v);
if (u != v) {
++ltt;
ans += edge[i].val;
fa[u] = v;
}
}
if (ltt < n - 1) {
printf("orz");
} else {
printf("%d", ans);
}
return;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d", &edge[i].from, &edge[i].to, &edge[i].val);
}
kruskal();
return 0;
}$Prim$ 算法
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#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 5;
const int inf = 2147483647;
int n, m, ans;
int g[5001][5001], vis[5005], dis[5005];
int minn = inf, k, v[5005];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; j++) {
g[i][j] = inf;
}
}
for (int i = 1; i <= m; ++i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (g[x][y] != inf) {
g[x][y] = min(z, g[x][y]);
g[y][x] = min(z, g[y][x]);
v[x] = v[y] = 1;
} else if (x == y) {
g[x][y] = 0;
} else {
g[x][y] = z;
g[y][x] = z;
v[x] = v[y] = 1;
}
}
for (int i = 1; i <= n; ++i) {
if (!v[i]) {
printf("orz");
return 0;
}
}
for (int i = 1; i <= n; ++i) {
dis[i] = inf;
}
dis[1] = 0;
for (int i = 1; i <= n; ++i) {
minn = inf;
k = -1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && minn > dis[j]) {
minn = dis[j];
k = j;
}
}
if (k == -1) {
printf("orz");
return 0;
}
vis[k] = 1;
ans += dis[k];
for (int j = 1; j <= n; j++) {
if (!vis[j] && dis[j] > g[k][j]) {
dis[j] = g[k][j];
}
}
}
printf("%d", ans);
return 0;
}
P3379 【模板】最近公共祖先
倍增
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
70
71
72
73#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m, s;
int tot, head[N];
int dep[N], f[N][21];
struct tree {
int from, to;
} edge[2 * N];
void addedge(int u, int v) {
edge[++tot] = (tree) {
v, head[u]
};
head[u] = tot;
}
void dfs(int son, int father) {
dep[son] = dep[father] + 1;
f[son][0] = father;
for (int i = 1; i <= 20; ++i) {
f[son][i] = f[f[son][i - 1]][i - 1];
}
for (int i = head[son]; i; i = edge[i].to) {
int v = edge[i].from;
if (v == father) {
continue;
}
dfs(v, son);
}
return;
}
int lca(int a, int b) {
if (dep[a] < dep[b]) {
swap(a, b);
}
for (int i = 20; i >= 0; --i) {
if (dep[f[a][i]] >= dep[b]) {
a = f[a][i];
}
}
if (a == b) {
return a;
}
for (int i = 20; i >= 0; --i) {
if (f[a][i] != f[b][i]) {
a = f[a][i];
b = f[b][i];
}
}
return f[a][0];
}
int main() {
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v), addedge(v, u);
}
dep[s] = 1;
lca(s, 0);
for (int i = 1; i <= m; ++i) {
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", lca(a, b));
}
return 0;
}$tarjan$
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<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m, s;
int tot, head[N];
int vis[N], fa[N], ans[N];
vector<pair<int, int>> ask[N];
struct tree {
int from, to;
} edge[2 * N];
void addedge(int u, int v) {
edge[++tot] = (tree) {
v, head[u]
};
head[u] = tot;
}
int find(int x) {
if (fa[x] == x) {
return x;
}
return fa[x] = find(fa[x]);
}
void tarjan(int u) {
vis[u] = 1;
for (int i = head[u]; i; i = edge[i].to) {
int v = edge[i].from;
if (!vis[v]) {
tarjan(v);
fa[v] = u;
}
}
for (auto q : ask[u]) {
if (vis[q.first]) {
ans[q.second] = find(q.first);
}
}
return;
}
int main() {
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v), addedge(v, u);
}
for (int i = 1; i <= m; ++i) {
int a, b;
scanf("%d%d", &a, &b);
ask[a].push_back({b, i});
ask[b].push_back({a, i});
}
for (int i = 1; i <= n; ++i) {
fa[i] = i;
}
tarjan(s);
for (int i = 1; i <= m; ++i) {
printf("%d\n", ans[i]);
}
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m, s;
int head[N], tot;
int dep[N], fa[N];
int zs_sz[N], zhong_son[N];
int top[N];
struct Node {
int from, to;
} edge[2 * N];
void addedge(int u, int v) {
edge[++tot] = (Node) {
v, head[u]
};
head[u] = tot;
return;
}
void dfs1(int son, int father) {
fa[son] = father;
dep[son] = dep[father] + 1;
zs_sz[son] = 1;
for (int i = head[son]; i; i = edge[i].to) {
int v = edge[i].from;
if (v == father) {
continue;
}
dfs1(v, son);
zs_sz[son] += zs_sz[v];
if (zs_sz[zhong_son[son]] < zs_sz[v]) {
zhong_son[son] = v;
}
}
return;
}
void dfs2(int son, int t) {
top[son] = t;
if (!zhong_son[son]) {
return;
}
dfs2(zhong_son[son], t);
for (int i = head[son]; i; i = edge[i].to) {
int v = edge[i].from;
if (v == fa[son] || v == zhong_son[son]) {
continue;
}
dfs2(v, v);
}
return;
}
int lca(int a, int b) {
while (top[a] != top[b]) {
if (dep[top[a]] < dep[top[b]]) {
swap(a, b);
}
a = fa[top[a]];
}
return ((dep[a] < dep[b]) ? a : b);
}
int main() {
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
dfs1(s, 0), dfs2(s, s);
for (int i = 1; i <= m; ++i) {
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", lca(a, b));
}
return 0;
}
P8306 【模板】字典树
1 | |
P1883 【模板】三分
给定 $n$ 个二次函数 $f_1(x),f_2(x),\dots,f_n(x)$(均形如 $ax^2+bx+c$),设 $F(x)=\max{f_1(x),f_2(x),…,f_n(x)}$,求 $F(x)$ 在区间 $[0,1000]$ 上的最小值。
1 | |
P1886 【模板】单调队列 / 滑动窗口
有一个长为 $n$ 的序列 $a$,以及一个大小为 $k$ 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
例如,对于序列 $[1,3,-1,-3,5,3,6,7]$ 以及 $k = 3$,有如下过程:
$$
\def\arraystretch{1.2}
\begin{array}{|c|c|c|}\hline
\textsf{窗口位置} & \textsf{最小值} & \textsf{最大值} \ \hline
\verb![1 3 -1] -3 5 3 6 7 ! & -1 & 3 \ \hline
\verb! 1 [3 -1 -3] 5 3 6 7 ! & -3 & 3 \ \hline
\verb! 1 3 [-1 -3 5] 3 6 7 ! & -3 & 5 \ \hline
\verb! 1 3 -1 [-3 5 3] 6 7 ! & -3 & 5 \ \hline
\verb! 1 3 -1 -3 [5 3 6] 7 ! & 3 & 6 \ \hline
\verb! 1 3 -1 -3 5 [3 6 7]! & 3 & 7 \ \hline
\end{array}
$$
1 | |
P5788 【模板】单调栈
定义函数 $f(i)$ 代表数列中第 $i$ 个元素之后第一个大于 $a_i$ 的元素的下标,即 $f(i)=\min_{i<j\leq n, a_j > a_i} {j}$。若不存在,则 $f(i)=0$,求出 $f(1\dots n)$。
1 | |
P3367 【模板】并查集
1 | |
P3811 【模板】模意义下的乘法逆元
给定 $n,p$,求 $1\sim n$ 中所有整数在模 $p$ 意义下的乘法逆元。
这里 $a$ 模 $p$ 的乘法逆元定义为 $ax\equiv1\pmod p$ 的解。
1 | |
P4549 【模板】裴蜀定理
对于任意整数 $a,b,d$,$gcd(a,b)|d \Leftrightarrow \exists \ u,v: ua+vb = d$
证明可以通过递归构造解来证明:
首先显然当 $u=1,v=0$ 的时候显然成立。假设存在 $u’,v’$,使得 $u’b+v’(a\ mod\ b)=d$.
注意 到 $$a\ mod\ b = a-\lfloor\frac{a}{b}\rfloor b$$,可得 $$ u’b + v’(a-\lfloor\frac{a}{b}\rfloor b) = d$$
整理可以得到:
$$u’b + v’a- v’\lfloor\frac{a}{b}\rfloor b = d$$
$$v’a + (u’-\lfloor\frac{a}{b}\rfloor v’)b = d$$
归纳证明该定理正确。
扩展到求 $ax+by=c$ 的最小非负 $c$,显然 $c$ 要满足 $gcd(a,b)|c$,所以 $c$ 取 $gcd(a,b)$ 是最小的。
扩展到这个题目,如果有多个数字的话,考虑前两个数字 $a_1,a_2$,有 $a_1x+a_2y=S \Rightarrow gcd(a_1,a_2)|d,$$gcd(a_1,a_2) =d$,所以把这两个的答案合并直接去取下一个就可以了。
1 | |