洛谷模板汇总
Graph Theory#
Disjoint Set##
【模板】并查集
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入输出格式
输入格式:
第一行包含两个整数N、M,表示共有N个元素和M个操作。
接下来M行,每行包含三个整数Zi、Xi、Yi
当Zi=1时,将Xi与Yi所在的集合合并
当Zi=2时,输出Xi与Yi是否在同一集合内,是的话输出Y;否则话输出N
输出格式:
如上,对于每一个Zi=2的操作,都有一行输出,每行包含一个大写字母,为Y或者N
输入输出样例
输入样例:
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4
输出样例:
N
Y
N
Y
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据,N<=10,M<=20;
对于70%的数据,N<=100,M<=1000;
对于100%的数据,N<=10000,M<=200000。
#include <iostream>
#define MAX_N 10000
using namespace std;
int n, m, f, x, y, father[MAX_N+5];
int getfather(int v) {
if (father[v] == v) {
return v;
}
father[v] = getfather(father[v]);
return father[v];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
father[i] = i;
}
for (int i = 0; i < m; i++) {
cin >> f >> x >> y;
if (f == 1) {
int f1 = getfather(x);
int f2 = getfather(y);
if (f1 != f2) {
father[f1] = f2;
}
} else {
int f1 = getfather(x);
int f2 = getfather(y);
if (f1 != f2) {
cout << "N" << endl; } else {
cout << "Y" << endl;
}
}
}
return 0;
}
Minimum Spanning Tree (Kruskal)##
【模板】最小生成树
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz
输入输出格式
输入格式:
第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)
接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi
输出格式:
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz
输入输出样例
输入样例:
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出样例:
7
说明
时空限制:1000ms,128M
数据规模:
对于20%的数据:N<=5,M<=20
对于40%的数据:N<=50,M<=2500
对于70%的数据:N<=500,M<=10000
对于100%的数据:N<=5000,M<=200000
#include <iostream>
#include <cstdio>
#include <algorithm>
#define MAX_N 5000
#define MAX_M 200000
using namespace std;
struct node {
int u, v, l;
} edge[MAX_M+5];
int n, m, tot = 0;
int father[MAX_N+5];
bool comp(const node &a, const node &b) {
return a.l < b.l;
}
int getfather(int v) {
if (father[v] == v) {
return v;
}
father[v] = getfather(father[v]);
return father[v];
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> edge[i].u >> edge[i].v >> edge[i].l;
}
sort(edge, edge+m, comp);
for (int i = 1; i <= n; i++) father[i] = i;
int flag = n-1;
for (int i = 0; i < m; i++) {
int f1 = getfather(edge[i].u);
int f2 = getfather(edge[i].v);
if (f1 != f2) {
father[f1] = f2;
tot += edge[i].l;
flag--;
}
if (flag == 0) break;
}
if (flag == 0) {
cout << tot;
} else {
cout << "orz";
}
return 0;
}
Shortest Path Fastest Algorithm##
【模板】单源最短路径
题目描述
如题,给出一个有向图,请输出从某一点出发到所有点的最短路径长度。
输入输出格式
输入格式:
第一行包含三个整数N、M、S,分别表示点的个数、有向边的个数、出发点的编号。
接下来M行每行包含三个整数Fi、Gi、Wi,分别表示第i条有向边的出发点、目标点和长度。
输出格式:
一行,包含N个用空格分隔的整数,其中第i个整数表示从点S出发到点i的最短路径长度(若S=i则最短路径长度为0,若从点S无法到达点i,则最短路径长度为2147483647)
输入输出样例
输入样例:
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
输出样例:
0 2 4 3
说明
时空限制:1000ms,128M
数据规模:
对于20%的数据:N<=5,M<=15
对于40%的数据:N<=100,M<=10000
对于70%的数据:N<=1000,M<=100000
对于100%的数据:N<=10000,M<=500000
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_M 500000
#define MAX_N 10000
#define INF 2147483647
using namespace std;
struct node {
int v, len, next;
node() {
v = len = next = 0;
}
} edge[MAX_M+5];
int n, m, s;
int first[MAX_N+5], cnt = 0;
int dis[MAX_N+5];
void insert(int u, int v, int l) {
cnt++;
edge[cnt].v = v;
edge[cnt].len = l;
edge[cnt].next = first[u];
first[u] = cnt;
}
void SPFA() {
for (int i = 1; i <= n; i++) {
dis[i] = INF;
}
int que[MAX_N*20+5], mark[MAX_N+5];
int head = 0, tail = 1;
memset(mark, 0, sizeof(mark));
que[1] = s;
mark[s] = 1;
dis[s] = 0;
while (head <= tail) {
head++;
for (int tmp = first[que[head]]; tmp; tmp = edge[tmp].next) {
if (dis[que[head]]+edge[tmp].len < dis[edge[tmp].v]) {
dis[edge[tmp].v] = dis[que[head]]+edge[tmp].len;
if (mark[edge[tmp].v] == 0) {
mark[edge[tmp].v] = 1;
tail++;
que[tail] = edge[tmp].v;
}
}
}
mark[que[head]] = 0;
}
}
int main() {
cin >> n >> m >> s;
memset(first, 0, sizeof(first));
int u, v, l;
for (int i = 0; i < m; i++) {
cin >> u >> v >> l;
insert(u, v, l);
}
SPFA();
for (int i = 1; i <= n; i++) {
cout << dis[i] << " ";
}
return 0;
}
Tarjan##
【模板】缩点
题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入输出格式
输入格式:
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。
输入输出样例
输入样例:
2 2
1 1
1 2
2 1
输出样例:
2
说明
n<=104,m<=105,|点权|<=1000 算法:Tarjan缩点+DAGdp
#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
#define MAX_N 100000
using namespace std;
int n, m, c[MAX_N+5], f[MAX_N+5];
int dfn[MAX_N+5], low[MAX_N+5], col[MAX_N+5], val[MAX_N+5], ind, cnt;
vector <int> G[MAX_N+5], E[MAX_N+5];
stack <int> sta;
bool insta[MAX_N+5];
void tarjan(int u) {
dfn[u] = low[u] = ++ind, sta.push(u), insta[u] = true;
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
else if (insta[v]) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
cnt++;
for (int i = sta.top(); ; i = sta.top()) {
col[i] = cnt, val[cnt] += c[i], insta[i] = false;
sta.pop(); if (u == i) break;
}
}
}
int DFS(int u) {
if (f[u]) return f[u];
int mx = 0;
for (int i = 0; i < E[u].size(); i++) mx = max(mx, DFS(E[u][i]));
return f[u] = val[u]+mx;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
while (m--) {
int u, v; scanf("%d%d", &u, &v);
G[u].push_back(v);
}
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i);
for (int u = 1; u <= n; u++) {
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i]; if (col[u] == col[v]) continue;
E[col[u]].push_back(col[v]);
}
}
int ans = 0;
for (int i = 1; i <= cnt; i++)
if (!f[i]) ans = max(ans, DFS(i));
printf("%d", ans);
return 0;
}
Cur Vertex##
【模板】割点
题目描述
给出一个n个点,m条边的无向图,求图的割点。
输入输出格式
输入格式:
第一行输入n,m
下面m行每行输入x,y表示x到y有一条边
输出格式:
第一行输出割点个数
第二行按照节点编号从小到大输出节点,用空格隔开
输入输出样例
输入样例:
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
输出样例:
1
5
说明
n,m均为100000
#include <iostream>
#include <cstdio>
#define MAX_N 100000
#define MAX_M 200000
using namespace std;
struct Edge {
int v, next;
} edge[MAX_M+5];
int first[MAX_N+5], flag[MAX_N+5], num[MAX_N+5], low[MAX_N+5];
int n, m, cnt = 0;
void insert(int u, int v, int pos) {
edge[pos].next = first[u];
edge[pos].v = v;
first[u] = pos;
}
void dfs(int cur, int father) {
int child = 0;
num[cur] = low[cur] = ++cnt;
for (int tmp = first[cur]; tmp; tmp = edge[tmp].next) {
if (!num[edge[tmp].v]) {
child++;
dfs(edge[tmp].v, cur);
low[cur] = min(low[cur], low[edge[tmp].v]);
if (low[edge[tmp].v] >= num[cur]) {
flag[cur] = 1;
}
} else if (num[edge[tmp].v] < num[cur] && edge[tmp].v != father) {
low[cur] = min(low[cur], num[edge[tmp].v]);
}
}
if (father == -1 && child == 1) {
flag[cur] = 0;
}
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
insert(u, v, i*2+1);
insert(v, u, i*2+2);
}
int tot = 0;
for (int i = 1; i <= n; i++) {
if (!num[i]) {
dfs(i, -1);
}
if (flag[i]) {
tot++;
}
}
cout << tot << endl;
for (int i = 1; i <= n; i++) {
if (flag[i]) {
cout << i << " ";
}
}
return 0;
}
Lowest Common Ancestor##
【模板】最近公共祖先 LCA
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入输出格式
输入格式:
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入样例:
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出样例:
4
4
1
4
4
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
#include <iostream>
#include <cstdio>
#include <vector>
#define MAX_N 500000
using namespace std;
struct Edge {
int next, to;
} edge[(MAX_N<<1)+5];
int n, m, s, x, y, cnt;
int d[MAX_N+5], p[MAX_N+5][25], first[MAX_N+5];
bool vis[MAX_N+5];
inline int read() {
int ret = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') ret = ret*10+ch-'0', ch = getchar();
return ret;
}
void INSERT(int a, int b) {
edge[++cnt].next = first[a];
edge[cnt].to = b;
first[a] = cnt;
}
void DFS(int u) {
vis[u] = true;
for (int i = 1; (1<<i) <= n; i++) {
if ((1<<i) <= d[u]) {
p[u][i] = p[p[u][i-1]][i-1];
}
}
for (int i = first[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (!vis[v]) {
d[v] = d[u]+1;
p[v][0] = u;
DFS(v);
}
}
}
int LCA(int a, int b) {
int i, j;
if (d[a] < d[b]) swap(a, b);
for (i = 0; (1<<i) <= d[a]; i++) {}
i--;
for (j = i; j >= 0; j--) {
if (d[a]-(1<<j) >= d[b]) {
a = p[a][j];
}
}
if (a == b) {
return a;
}
for (j = i; j >= 0; j--) {
if (p[a][j] != p[b][j]) {
a = p[a][j];
b = p[b][j];
}
}
return p[a][0];
}
int main() {
n = read(), m = read(), s = read();
for (int i = 1; i < n; i++) {
x = read(), y = read();
INSERT(x, y);
INSERT(y, x);
}
DFS(s);
for (int i = 0; i < m; i++) {
x = read(), y = read();
cout << LCA(x, y) << endl;
}
return 0;
}
Negative Loop##
【模板】负环
题目描述
暴力枚举/SPFA/Bellman-ford/奇怪的贪心/超神搜索
输入输出格式
输入格式:
第一行一个正整数T表示数据组数,对于每组数据:
第一行两个正整数N M,表示图有N个顶点,M条边
接下来M行,每行三个整数a b w,表示a->b有一条权值为w的边(若w<0则为单向,否则双向)
输出格式:
共T行。对于每组数据,存在负环则输出一行"YE5"(不含引号),否则输出一行"N0"(不含引号)。
输入输出样例
输入样例#1:
2
3 4
1 2 2
1 3 4
2 3 1
3 1 -3
3 3
1 2 3
2 3 4
3 1 -8
输出样例#1:
N0
YE5
说明
N,M,|w|≤200 000;1≤a,b≤N;T≤10 建议复制输出格式中的字符串。
此题普通Bellman-Ford或BFS-SPFA会TLE
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define MAX_N 200000
#define SIZE 25000000
using namespace std;
int f; char BUF[SIZE], *buf = BUF;
inline void read(int &x) {
bool flag = 0; while (*buf < 48) if (*buf++ == 45) flag = 1;
x = 0; while (*buf > 32) x = x*10+*buf++-48; x = flag ? -x : x;
}
vector <int> G[MAX_N+5], E[MAX_N+5];
int dis[MAX_N+5];
bool insta[MAX_N+5], flag;
void init(int n) {
flag = false;
for (int i = 1; i <= n; i++) G[i].clear(), E[i].clear();
memset(dis, 0, sizeof(dis)), memset(insta, false, sizeof(insta));
}
void DFS(int u) {
insta[u] = true;
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i], c = E[u][i];
if (dis[u]+c >= dis[v]) continue;
if (insta[v] || flag) {flag = true; break;}
dis[v] = dis[u]+c, DFS(v);
}
insta[u] = false;
}
int main() {
f = fread(BUF, 1, SIZE, stdin);
int T; read(T);
while (T--) {
int n, m; read(n), read(m); init(n);
while (m--) {
int u, v, c; read(u), read(v), read(c);
G[u].push_back(v), E[u].push_back(c);
if (c >= 0) G[v].push_back(u), E[v].push_back(c);
}
for (int i = 1; i <= n; i++) {DFS(i); if (flag) break;}
if (flag) printf("YE5\n");
else printf("N0\n");
}
return 0;
}
Network Flow (Dinic)##
【模板】网络最大流
题目描述
给出一个网络图,以及其源点和汇点,求出其网络最大流。
输入输出格式
输入格式:
第一行包含四个正整数N、M、S、T,分别表示点的个数、有向边的个数、源点序号、汇点序号。
接下来M行每行包含三个正整数ui、vi、wi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi)
输出格式:
一行,包含一个正整数,即为该网络的最大流。
输入输出样例
输入样例:
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
输出样例:
50
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=25
对于70%的数据:N<=200,M<=1000
对于100%的数据:N<=10000,M<=100000
样例说明:
题目中存在3条路径:
4-->2-->3,该路线可通过20的流量
4-->3,可通过20的流量
4-->2-->1-->3,可通过10的流量(边4-->2之前已经耗费了20的流量)
故流量总计20+20+10=50。输出50。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define MAX_N 10000
#define MAX_M 100000
#define INF 2147483647
using namespace std;
struct node {
int v, c, next;
} E[MAX_M*2+5];
int first[MAX_N+5], cnt;
int n, m, s, t;
int d[MAX_N+5];
void init() {
cnt = 0;
memset(first, -1, sizeof(first));
}
void insert(int u, int v, int c) {
E[cnt].v = v, E[cnt].c = c;
E[cnt].next = first[u];
first[u] = cnt++;
}
bool BFS() {
memset(d, -1, sizeof(d));
queue <int> que;
que.push(s);
d[s] = 0;
while (!que.empty()) {
int u = que.front();
for (int i = first[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (E[i].c && d[v] == -1) {
que.push(v);
d[v] = d[u]+1;
}
}
que.pop();
}
return d[t] != -1;
}
int DFS(int u, int flow) {
if (u == t) {
return flow;
}
int ret = 0;
for (int i = first[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (E[i].c && d[u]+1 == d[v]) {
int tmp = DFS(v, min(flow, E[i].c));
flow -= tmp, E[i].c -= tmp, ret += tmp;
E[i^1].c += tmp;
if (flow == 0) break;
}
}
if (ret == 0) d[u] = -1;
return ret;
}
int main() {
init();
cin >> n >> m >> s >> t;
for (int i = 0; i < m; i++) {
int u, v, c;
cin >> u >> v >> c;
insert(u, v, c);
insert(v, u, 0);
}
int ans = 0;
while (BFS()) {
ans += DFS(s, INF);
}
cout << ans;
return 0;
}
Minimum Cost Maximum Flow##
【模板】最小费用最大流
题目描述
给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用。
输入输出格式
输入格式:
第一行包含四个正整数N、M、S、T,分别表示点的个数、有向边的个数、源点序号、汇点序号。
接下来M行每行包含四个正整数ui、vi、wi、fi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi),单位流量的费用为fi。
输出格式:
一行,包含两个整数,依次为最大流量和在最大流量情况下的最小费用。
输入输出样例
输入样例:
4 5 4 3
4 2 30 2
4 3 20 3
2 3 20 1
2 1 30 9
1 3 40 5
输出样例:
50 280
说明
时空限制:1000ms,128M
(BYX:最后两个点改成了1200ms)
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=1000,M<=1000
对于100%的数据:N<=5000,M<=50000
样例说明:
最优方案如下:
第一条流为4-->3,流量为20,费用为320=60。
第二条流为4-->2-->3,流量为20,费用为(2+1)20=60。
第三条流为4-->2-->1-->3,流量为10,费用为(2+9+5)*10=160。
故最大流量为50,在此状况下最小费用为60+60+160=280。
故输出50 280。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define MAX_N 5000
#define MAX_M 50000
#define INF 2147483647
using namespace std;
int n, m, s, t, tot, ans;
int pre[MAX_N+5], match[MAX_N+5], cnt;
struct node {int u, v, c, w, nxt;} E[MAX_M*2+MAX_N*2+5];
void init() {cnt = 0; memset(pre, -1, sizeof(pre));}
void insert(int u, int v, int c, int w) {E[cnt].u = u, E[cnt].v = v, E[cnt].c = c, E[cnt].w = w, E[cnt].nxt = pre[u], pre[u] = cnt++;}
bool SPFA() {
queue <int> que;
bool inque[MAX_N+5];
int dis[MAX_N+5], pree[MAX_N+5];
memset(inque, false, sizeof(inque));
for (int i = 1; i <= n; i++) dis[i] = INF;
memset(pree, -1, sizeof(pree));
dis[s] = 0, que.push(s), inque[s] = true;
while (!que.empty()) {
int u = que.front();
for (int i = pre[u]; i != -1; i = E[i].nxt) {
int v = E[i].v;
if (E[i].c && dis[u]+E[i].w < dis[v]) {
dis[v] = dis[u]+E[i].w, pree[v] = i;
if (!inque[v]) que.push(v), inque[v] = true;
}
}
que.pop(), inque[u] = false;
}
if (dis[t] == INF) return false;
int flow = INF;
for (int i = pree[t]; i != -1; i = pree[E[i].u]) flow = min(flow, E[i].c);
for (int i = pree[t]; i != -1; i = pree[E[i].u]) E[i].c -= flow, E[i^1].c += flow;
tot += flow, ans += dis[t]*flow;
return true;
}
int main() {
scanf("%d%d%d%d", &n, &m, &s, &t);
init();
for (int i = 0; i < m; i++) {
int u, v, c, w;
scanf("%d%d%d%d", &u, &v, &c, &w);
insert(u, v, c, w), insert(v, u, 0, -w);
}
while (SPFA()) ;
printf("%d %d", tot, ans);
return 0;
}
Bipartite Matching##
【模板】二分图匹配
题目描述
给定一个二分图,结点个数分别为n,m,边数为e,求二分图最大匹配数
输入输出格式
输入格式:
第一行,n,m,e
第二至e+1行,每行两个正整数u,v,表示u,v有一条连边
输出格式:
共一行,二分图最大匹配
输入输出样例
输入样例:
1 1 1
1 1
输出样例:
1
说明
n,m<=1000,1<=u<=n,1<=v<=m
Hungary###
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define MAX_N 1000
using namespace std;
int n, m, e, match[MAX_N+5], cnt;
vector <int> G[MAX_N+5];
bool vis[MAX_N+5];
bool DFS(int u) {
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!vis[v]) {
vis[v] = true;
if (!match[v] || DFS(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main() {
cin >> n >> m >> e;
int a, b;
for (int i = 0; i < e; i++) {
cin >> a >> b;
if (a > n || b > m) continue;
G[a].push_back(b);
}
for (int i = 1; i <= n; i++) {
memset(vis, false, sizeof(vis));
if (DFS(i)) {
cnt++;
}
}
cout << cnt;
return 0;
}
Dinic###
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define MAX_N 2000
#define MAX_M 1000000
#define INF 2147483647
using namespace std;
int n, m, s, t, n1, n2;
int pre[MAX_N+5], tmp[MAX_N+5], d[MAX_N+5], cnt;
struct node {int v, c, nxt;} E[MAX_M*2+MAX_N*2+5];
void init() {cnt = 0; s = 0, t = n; memset(pre, -1, sizeof(pre));}
void insert(int u, int v, int c) {E[cnt].v = v, E[cnt].c = c, E[cnt].nxt = pre[u], pre[u] = cnt++;}
bool BFS() {
memset(d, -1, sizeof(d));
queue <int> que;
que.push(s), d[s] = 0;
while (!que.empty()) {
int u = que.front();
for (int i = pre[u]; i != -1; i = E[i].nxt) {
int v = E[i].v;
if (E[i].c && d[v] == -1) {
d[v] = d[u]+1;
que.push(v);
}
}
que.pop();
}
return d[t] != -1;
}
int DFS(int u, int flow) {
if (u == t) return flow;
int ret = 0;
for (int &i = pre[u]; i != -1; i = E[i].nxt) {
int v = E[i].v;
if (E[i].c && d[u]+1 == d[v]) {
int tmp = DFS(v, min(flow, E[i].c));
E[i].c -= tmp, E[i^1].c += tmp, flow -= tmp, ret += tmp;
if (!flow) break;
}
}
if (!ret) d[u] = -1;
return ret;
}
int Dinic() {
int ret = 0;
for (int i = 0; i <= n; i++) tmp[i] = pre[i];
while (BFS()) {
ret += DFS(s, INF);
for (int i = 0; i <= n; i++) pre[i] = tmp[i];
}
return ret;
}
int main() {
scanf("%d%d%d", &n1, &n2, &m); n = n1+n2+1;
init();
for (int i = 1; i <= n1; i++) insert(s, i, 1), insert(i, s, 0);
for (int i = n1+1; i <= n1+n2; i++) insert(i, t, 1), insert(t, i, 0);
for (int i = 0; i < m; i++) {
int u, v;
scanf("%d%d", &u, &v);
if (u > n1 || v > n2) continue;
insert(u, n1+v, 1), insert(n1+v, u, 0);
}
printf("%d", Dinic());
return 0;
}
Math Theory#
Gauss Elimination##
【模板】高斯消元
题目背景
Gauss消元
题目描述
给定一个线性方程组,对其求解
输入输出格式
输入格式:
第一行,一个正整数n
第二至n+1行,每行n+1个整数,为a1,a2,...an和b,代表一组方程。
输出格式:
共n行,每行一个数,第i行为xi(保留2位小数)
如果无解或不存在唯一解,在第一行输出"No Solution".
输入输出样例
输入样例#1:
3
1 3 4 5
1 4 7 3
9 3 2 2
输出样例#1:
-0.97
5.18
-2.39
说明
1≤n≤100,|ai|≤1e4,|b|≤1e4
#include <iostream>
#include <cstdio>
#include <vector>
#define MAX_N 100
#define EXP 1e-7
using namespace std;
typedef double fnt;
int n; vector <fnt> f[MAX_N+5]; fnt ans[MAX_N+5];
bool gauss() {
for (int i = 1, tmp; i <= n; i++) {
for (tmp = i; tmp <= n; tmp++) if (f[tmp][i] <= -EXP || f[tmp][i] >= EXP) break;
if (tmp > n) return false; swap(f[i], f[tmp]);
for (int j = 1; j <= n; j++) {
fnt div = f[j][i]/f[i][i]; if (j == i) continue;
for (int k = i; k <= n+1; k++) f[j][k] -= f[i][k]*div;
}
}
for (int i = 1; i <= n; i++) ans[i] = f[i][n+1]/f[i][i];
return true;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
f[i].push_back(0);
for (int j = 1; j <= n+1; j++) {
fnt x; scanf("%lf", &x);
f[i].push_back(x);
}
}
if (gauss()) for (int i = 1; i <= n; i++) printf("%.2lf\n", ans[i]);
else printf("No Solution");
return 0;
}
Inverse##
【模板】乘法逆元
题目背景
这是一道模板题
题目描述
给定n,p求1~n中所有整数在模p意义下的乘法逆元。
输入输出格式
输入格式:
一行n,p
输出格式:
n行,第i行表示i在模p意义下的逆元。
输入输出样例
输入样例#1:
10 13
输出样例#1:
1
7
9
10
8
11
2
5
3
4
说明
1≤n≤3*1e6,n<p<20000528
输入保证p为质数。
#include <iostream>
#include <cstdio>
#define MAX_N 3000000
using namespace std;
typedef long long lnt;
lnt inv[MAX_N+5];
void init(int n, lnt p) {inv[0] = inv[1] = 1; for (int i = 2; i <= n; i++) inv[i] = (p-p/i*inv[p%i]%p)%p;}
int main() {
int n; lnt p; scanf("%d%lld", &n, &p);
init(n, p);
for (int i = 1; i <= n; i++) printf("%lld\n", inv[i]);
return 0;
}
Linear Basis##
【模板】线性基
题目背景
这是一道模板题。
题目描述
给定n个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
输入输出格式
输入格式:
第一行一个数n,表示元素个数
接下来一行n个数
输出格式:
仅一行,表示答案。
输入输出样例
输入样例#1:
2
1 1
输出样例#1:
1
说明
1≤n≤50,0≤Si≤2^50
#include <iostream>
#include <cstdio>
#define MAX_N 50
using namespace std;
typedef long long lnt;
int n; lnt base[MAX_N+5], pow[MAX_N+5];
int main() {
scanf("%d", &n); pow[0] = 1;
for (int i = 1; i <= MAX_N; i++) pow[i] = pow[i-1]*2;
for (int i = 1; i <= n; i++) {
lnt x; scanf("%lld", &x);
for (int j = MAX_N; j >= 0; j--) if (pow[j]&x) {
if (base[j]) x ^= base[j];
else {base[j] = x; break;}
}
}
lnt ans = 0;
for (int i = MAX_N; i >= 0; i--) if ((ans^pow[i]) > ans) ans ^= base[i];
printf("%lld", ans);
return 0;
}
Lucas##
【模板】卢卡斯定理
题目背景
这是一道模板题。
题目描述
给定n,m,p(1≤n,m,p≤1e5),求C(n+m,m)。
保证P为prime
一个测试点内包含多组数据。
输入输出格式
输入格式:
第一行一个整数T(T≤10),表示数据组数
第二行开始共T行,每行三个数n m p,意义如上
输出格式:
共T行,每行一个整数表示答案。
输入输出样例
输入样例#1:
2
1 2 5
2 1 5
输出样例#1:
3
3
#include <iostream>
#include <cstdio>
#define MAX_P 100000
using namespace std;
typedef long long lnt;
lnt f[MAX_P+5] = {1};
void init(lnt p) {for (int i = 1; i <= MAX_P; i++) f[i] = f[i-1]*i%p;}
lnt FLT(lnt x, lnt p) {
lnt ret = 1; x %= p;
for (int k = p-2; k; k >>= 1) ret = k%2 ? ret*x%p : ret, x = x*x%p;
return ret;
}
lnt lucas(lnt n, lnt m, lnt p) {return m ? (n%p >= m%p ? f[n%p]*FLT(f[m%p]*f[n%p-m%p], p)*lucas(n/p, m/p, p)%p : 0) : 1;}
int main() {
int T; scanf("%d", &T);
while (T--) {
lnt n, m, p; scanf("%lld%lld%lld", &n, &m, &p);
init(p), printf("%lld\n", lucas(n+m, min(n, m), p)%p);
}
return 0;
}
Matrix Fast Power##
【模板】矩阵快速幂
题目描述
给定n*n的矩阵A,求A^k
输入输出格式
输入格式:
第一行,n,k
第2至n+1行,每行n个数,第i+1行第j个数表示矩阵第i行第j列的元素
输出格式:
输出A^k
共n行,每行n个数,第i行第j个数表示矩阵第i行第j列的元素,每个元素模10^9+7
输入输出样例
输入样例:
2 1
1 1
1 1
输出样例:
1 1
1 1
说明
n<=100, k<=10^12, |矩阵元素|<=1000
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_N 100
#define MOD 1000000007
using namespace std;
int n;
struct Matrix {
long long ele[MAX_N][MAX_N];
inline Matrix operator * (const Matrix &x) const {
Matrix ret;
memset(ret.ele, 0, sizeof(ret.ele));
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
ret.ele[i][j] = (ret.ele[i][j]+ele[i][k]*x.ele[k][j])%MOD;
return ret;
}
};
Matrix Power(Matrix a, long long k) {
if (k == 1) return a;
Matrix ret = Power(a, k/2);
if (k%2) return a*ret*ret;
return ret*ret;
}
int main() {
long long k;
Matrix a;
cin >> n >> k;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> a.ele[i][j];
a = Power(a, k);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++)
cout << a.ele[i][j] << " ";
cout << endl;
}
return 0;
}
Prime Sieve##
【模板】线性筛素数
题目描述
如题,给定一个范围N,你需要处理M个某数字是否为质数的询问(每个数字均在范围1-N内)
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示查询的范围和查询的个数。
接下来M行每行包含一个不小于1且不大于N的整数,即询问概数是否为质数。
输出格式:
输出包含M行,每行为Yes或No,即依次为每一个询问的结果。
输入输出样例
输入样例:
100 5
2
3
4
91
97
输出样例:
Yes
Yes
No
No
Yes
说明
时空限制:500ms 128M
数据规模:
对于30%的数据:N<=10000,M<=10000
对于100%的数据:N<=10000000,M<=100000
样例说明:
N=100,说明接下来的询问数均不大于100且大于1。
所以2、3、97为质数,4、91非质数。
故依次输出Yes、Yes、No、No、Yes。
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_N 10000000
using namespace std;
int n, m;
bool IsPrime[MAX_N+5];
int pri[MAX_N+5], cnt = 0;
void FindPrime() {
IsPrime[0] = IsPrime[1] = false;
for (int i = 2; i <= n; i++) {
if (IsPrime) {
pri[cnt++] = i;
}
for (int j = 0; j < cnt; j++) {
if (i*pri[j] > n) break;
IsPrime[i*pri[j]] = false;
if (i%pri[j] == 0) break;
}
}
}
int main() {
memset(IsPrime, true, sizeof(IsPrime));
cin >> n;
FindPrime();
cin >> m;
for (int i = 0; i < m; i++) {
int x;
cin >> x;
if (IsPrime[x]) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
return 0;
}
Trigeminal Search##
【模板】三分法
题目描述
如题,给出一个N次函数,保证在范围[l,r]内存在一点x,使得[l,x]上单调增,[x,r]上单调减。试求出x的值。
输入输出格式
输入格式:
第一行一次包含一个正整数N和两个实数l、r,含义如题目描述所示。
第二行包含N+1个实数,从高到低依次表示该N次函数各项的系数。
输出格式:
输出为一行,包含一个实数,即为x的值。四舍五入保留5位小数。
输入输出样例
输入样例:
3 -0.9981 0.5
1 -3 -3 1
输出样例:
-0.41421
说明
时空限制:50ms,128M
数据规模:
对于100%的数据:7<=N<=13
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int n;
double s, t;
double a[14];
double calc(double x) {
double tot = 0, tmp = 1;
for (int i = 0; i <= n; i++) {
tot += tmp*a[i];
tmp *= x;
}
return tot;
}
void f(double l, double r) {
if (abs(r-l) <= 0.000001) {
printf("%.5f", l);
return;
}
double ml = (2*l+r)/3, mr = (l+2*r)/3;
if (calc(ml) > calc(mr)) {
f(l, mr);
} else {
f(ml, r);
}
}
int main() {
cin >> n >> s >> t;
for (int i = n; i >= 0; i--) {
cin >> a[i];
}
f(s, t);
return 0;
}
Data Structure#
Heap##
【模板】堆
题目描述
如题,初始小根堆为空,我们需要支持以下3种操作:
操作1: 1 x 表示将x插入到堆中
操作2: 2 输出该小根堆内的最小数
操作3: 3 删除该小根堆内的最小数
输入输出格式
输入格式:
第一行包含一个整数N,表示操作的个数
接下来N行,每行包含1个或2个正整数,表示三种操作,格式如下:
操作1: 1 x
操作2: 2
操作3: 3
输出格式:
包含若干行正整数,每行依次对应一个操作2的结果。
输入输出样例
输入样例:
5
1 2
1 5
2
3
2
输出样例:
2
5
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=15
对于70%的数据:N<=10000
对于100%的数据:N<=1000000
#include <iostream>
using namespace std;
int n, heap[1000000+5], size = 0;
void insert(int x) {
size++;
heap[size] = x;
int current = size;
int father = current/2;
while (father > 0 && heap[father] > heap[current]) {
swap(heap[father], heap[current]);
current = father;
father = current/2;
}
}
void pop() {
heap[1] = heap[size];
size--;
int current = 1;
int child = current*2;
if (child < size && heap[child] > heap[child+1]) child++;
while (child <= size && heap[current] > heap[child]) {
swap(heap[current], heap[child]);
current = child;
child = 2*current;
if (child < size && heap[child] > heap[child+1]) child++;
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
int f;
cin >> f;
if (f == 1) {
int x;
cin >> x;
insert(x);
} else if (f == 2) {
cout << heap[1] << endl;
} else {
pop();
}
}
return 0;
}
Mergeable Heap##
【模板】左偏树(可并堆)
题目描述
如题,一开始有N个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:
操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删除或第x和第y个数在用一个堆内,则无视此操作)
操作2: 2 x 输出第x个数所在的堆最小数,并将其删除(若第x个数已经被删除,则输出-1并无视删除操作)
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示一开始小根堆的个数和接下来操作的个数。
第二行包含N个正整数,其中第i个正整数表示第i个小根堆初始时包含且仅包含的数。
接下来M行每行2个或3个正整数,表示一条操作,格式如下:
操作1 : 1 x y
操作2 : 2 x
输出格式:
输出包含若干行整数,分别依次对应每一个操作2所得的结果。
输入输出样例
输入样例#1:
5 5
1 5 4 2 3
1 1 5
1 2 5
2 2
1 4 2
2 2
输出样例#1:
1
2
说明
当堆里有多个最小值时,优先删除原序列的靠前的,否则会影响后续操作1导致WA。
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=1000,M<=1000
对于100%的数据:N<=100000,M<=100000
样例说明:
初始状态下,五个小根堆分别为:{1}、{5}、{4}、{2}、{3}。
第一次操作,将第1个数所在的小根堆与第5个数所在的小根堆合并,故变为四个小根堆:{1,3}、{5}、{4}、{2}。
第二次操作,将第2个数所在的小根堆与第5个数所在的小根堆合并,故变为三个小根堆:{1,3,5}、{4}、{2}。
第三次操作,将第2个数所在的小根堆的最小值输出并删除,故输出1,第一个数被删除,三个小根堆为:{3,5}、{4}、{2}。
第四次操作,将第4个数所在的小根堆与第2个数所在的小根堆合并,故变为两个小根堆:{2,3,5}、{4}。
第五次操作,将第2个数所在的小根堆的最小值输出并删除,故输出2,第四个数被删除,两个小根堆为:{3,5}、{4}。
故输出依次为1、2。
#include <iostream>
#include <cstdio>
#define MAX_N 100000
using namespace std;
struct node {int val, dis, ls, rs;} heap[MAX_N+5];
int n, m, fa[MAX_N+5];
int getf(int x) {return fa[x] == x ? fa[x] : getf(fa[x]);}
int merge(int a, int b) {
if (!a || !b) return a^b;
if (heap[a].val > heap[b].val || (heap[a].val == heap[b].val && a > b)) swap(a, b);
heap[a].rs = merge(heap[a].rs, b), fa[heap[a].rs] = a;
if (heap[heap[a].rs].dis > heap[heap[a].ls].dis) swap(heap[a].ls, heap[a].rs);
heap[a].dis = heap[a].rs == 0 ? 0 : heap[heap[a].rs].dis+1; return a;
}
int pop(int a) {
int l = heap[a].ls, r = heap[a].rs;
heap[a].ls = heap[a].rs = heap[a].val = 0, fa[l] = l, fa[r] = r;
return merge(l, r);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &heap[i].val), fa[i] = i;
while (m--) {
int opt; scanf("%d", &opt);
if (opt == 1) {
int x, y; scanf("%d%d", &x, &y), x = getf(x), y = getf(y);
if (heap[x].val && heap[y].val && x != y) merge(x, y);
}
if (opt == 2) {
int x; scanf("%d", &x), x = getf(x);
if (!heap[x].val) printf("-1\n");
else printf("%d\n", heap[x].val), pop(x);
}
}
return 0;
}
Binary Indexed Tree##
1###
【模板】树状数组 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某一个数加上x
2.求出某区间每一个数的和
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x k 含义:将第x个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入输出样例
输入样例:
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
输出样例:
14
16
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_N 500000
using namespace std;
int n, m;
int tree[MAX_N+5];
int lowbit(int x) {
return x&(-x);
}
void modify(int pos, int x) {
while (pos <= n) {
tree[pos] += x;
pos += lowbit(pos);
}
}
long long query(int t) {
long long tot = 0;
while (t > 0) {
tot += tree[t];
t -= lowbit(t);
}
return tot;
}
int main() {
memset(tree, 0, sizeof(tree));
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int tmp;
cin >> tmp;
modify(i, tmp);
}
for (int i = 0; i < m; i++) {
int f, a, b;
cin >> f >> a >> b;
if (f == 1) {
modify(a, b);
} else {
cout << query(b)-query(a-1) << endl;
}
}
return 0;
}
2###
【模板】树状数组 2
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数数加上x
2.求出某一个数
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x 含义:输出第x个数的值
输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入输出样例
输入样例:
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4
输出样例:
6
10
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_N 500000
using namespace std;
int n, m;
int tree[MAX_N+5];
int lowbit(int x) {
return x&(-x);
}
void modify(int pos, int x) {
while (pos <= n) {
tree[pos] += x;
pos += lowbit(pos);
}
}
long long query(int pos) {
long long tot = 0;
while (pos > 0) {
tot += tree[pos];
pos -= lowbit(pos);
}
return tot;
}
int main() {
memset(tree, 0, sizeof(tree));
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
modify(i, x);
modify(i+1, -x);
}
for (int i = 0; i < m; i++) {
int f;
cin >> f;
if (f == 1) {
int l, r, x;
cin >> l >> r >> x;
modify(l, x);
modify(r+1, -x);
} else {
int x;
cin >> x;
cout << query(x) << endl;
}
}
return 0;
}
Segment Tree##
1###
【模板】线段树 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入输出样例
输入样例:
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例:
11
8
20
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
(数据保证在int64/long long数据范围内)
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_N 100000
using namespace std;
int n, k;
long long tree[MAX_N*4+50], tag[MAX_N*4+50];
void updata(int v) {
tree[v] = tree[v*2]+tree[v*2+1];
}
void downtag(int v, int s, int t) {
tag[v*2] += tag[v];
tag[v*2+1] += tag[v];
int m = (s+t)/2;
tree[v*2] += tag[v]*(m-s+1);
tree[v*2+1] += tag[v]*(t-m);
tag[v] = 0;
}
void modify(int v, int s, int t, int l, int r, int x) {
if (s >= l && t <= r) {
tree[v] += x*(t-s+1);
tag[v] += x;
return;
}
int m = (s+t)/2;
downtag(v, s, t);
if (l <= m) {
modify(v*2, s, m, l, r, x);
}
if (r >= m+1) {
modify(v*2+1, m+1, t, l, r, x);
}
updata(v);
}
void build(int v, int s, int t) {
if (s == t) {
cin >> tree[v];
return;
}
int m = (s+t)/2;
build(v*2, s, m);
build(v*2+1, m+1, t);
updata(v);
}
long long query(int v, int s, int t, int l, int r) {
if (s >= l && t <= r) {
return tree[v];
}
int m = (s+t)/2;
downtag(v, s, t);
long long ret = 0;
if (l <= m) {
ret += query(v*2, s, m, l, r);
}
if (r >= m+1) {
ret += query(v*2+1, m+1, t, l, r);
}
updata(v);
return ret;
}
int main() {
cin >> n >> k;
build(1, 1, n);
for (int i = 0; i < k; i++) {
int f, l, r, x;
cin >> f;
if (f == 1) {
cin >> l >> r >> x;
modify(1, 1, n, l, r, x);
} else {
cin >> l >> r;
cout << query(1, 1, n, l, r) << endl;
}
}
return 0;
}
2###
【模板】线段树 2
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.将某区间每一个数乘上x
3.求出某区间每一个数的和
输入输出格式
输入格式:
第一行包含三个整数N、M、P,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k
操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k
操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对P取模所得的结果
输出格式:
输出包含若干行整数,即为所有操作3的结果。
输入输出样例
输入样例:
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
输出样例:
17
2
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_N 100000
#define ll long long
using namespace std;
int n, m;
ll p;
ll tree[MAX_N*4+5], mul[MAX_N*4+5], add[MAX_N*4+5];
void updata(int v) {
tree[v] = (tree[v*2]+tree[v*2+1])%p;
}
void downtag(int v, int s, int t, int mid) {
if (mul[v] == 1 && add[v] == 0) return;
mul[v*2] = mul[v*2]*mul[v]%p;
add[v*2] = (add[v*2]*mul[v]%p+add[v])%p;
tree[v*2] = (tree[v*2]*mul[v]%p+add[v]*(ll)(mid-s+1)%p)%p;
mul[v*2+1] = mul[v*2+1]*mul[v]%p;
add[v*2+1] = (add[v*2+1]*mul[v]%p+add[v])%p;
tree[v*2+1] = (tree[v*2+1]*mul[v]%p+add[v]*(ll)(t-mid)%p)%p;
mul[v] = 1;
add[v] = 0;
return;
}
void create(int v, int s, int t) {
mul[v] = 1;
add[v] = 0;
if (s == t) {
cin >> tree[v];
tree[v] %= p;
return;
}
int mid = (s+t)/2;
create(v*2, s, mid);
create(v*2+1, mid+1, t);
updata(v);
}
void modify1(int v, int s, int t, int l, int r, int x) {
if (s >= l && t <= r) {
add[v] = (add[v]+(ll)x)%p;
tree[v] = (tree[v]+(ll)x*(ll)(t-s+1)%p)%p;
return;
}
int mid = (s+t)/2;
downtag(v, s, t, mid);
if (l <= mid) {
modify1(v*2, s, mid, l, r, x);
}
if (r >= mid+1) {
modify1(v*2+1, mid+1, t, l, r, x);
}
updata(v);
}
void modify2(int v, int s, int t, int l, int r, int x) {
if (s >= l && t <= r) {
mul[v] = mul[v]*(ll)x%p;
add[v] = add[v]*(ll)x%p;
tree[v] = tree[v]*(ll)x%p;
return;
}
int mid = (s+t)/2;
downtag(v, s, t, mid);
if (l <= mid) {
modify2(v*2, s, mid, l, r, x);
}
if (r >= mid+1) {
modify2(v*2+1, mid+1, t, l, r, x);
}
updata(v);
}
ll query(int v, int s, int t, int l, int r) {
if (s >= l && t <= r) {
return tree[v];
}
int mid = (s+t)/2;
ll ret = 0;
downtag(v, s, t, mid);
if (l <= mid) {
ret = (ret+query(v*2, s, mid, l, r))%p;
}
if (r >= mid+1) {
ret = (ret+query(v*2+1, mid+1, t, l, r))%p;
}
updata(v);
return ret;
}
int main() {
cin >> n >> m >> p;
create(1, 1, n);
for (int i = 0; i < m; i++) {
int f;
cin >> f;
if (f == 1) {
int l, r, x;
cin >> l >> r >> x;
modify2(1, 1, n, l, r, x%p);
} else if (f == 2) {
int l, r, x;
cin >> l >> r >> x;
modify1(1, 1, n, l, r, x%p);
} else {
int l, r;
cin >> l >> r;
cout << query(1, 1, n, l, r) << endl;
}
}
return 0;
}
Sparse Table##
【模板】ST表
题目背景
这是一道ST表经典题——静态区间最大值
请注意最大数据时限只有0.8s,数据强度不低,请务必保证你的每次查询复杂度为O(1)
题目描述
给定一个长度为N的数列,和M次询问,求出每一次询问的区间内数字的最大值。
输入输出格式
输入格式:
第一行包含两个整数N,M,分别表示数列的长度和询问的个数。
第二行包含N个整数(记为ai),依次表示数列的第i项。
接下来M行,每行包含两个整数li,ri,表示查询的区间为[li,ri]。
输出格式:
输出包含M行,每行一个整数,依次表示每一次询问的结果。
输入输出样例
输入样例#1:
8 8
9 3 1 7 5 6 0 8
1 6
1 5
2 7
2 6
1 8
4 8
3 7
1 8
输出样例#1:
9
9
7
7
9
8
7
9
说明
对于30%的数据,满足:1≤N,M≤10
对于70%的数据,满足:1≤N,M≤1e5
对于100%的数据,满足:1≤N≤1e5,1≤M≤1e6,ai∈[0,1e9],1≤li≤ri≤N
#include <iostream>
#include <cstdio>
#include <cmath>
#define MAX_N 100000
using namespace std;
int n, m, num[MAX_N+5], mx[MAX_N+5][20];
void setTable() {
for (int i = 1; i <= n; i++) mx[i][0] = num[i];
for (int j = 1; (1<<j) <= n; j++)
for (int i = 1; i+(1<<j)-1 <= n; i++)
mx[i][j] = max(mx[i][j-1], mx[i+(1<<j-1)][j-1]);
}
int query(int l, int r) {
int range = (int)(log(r-l+1)/log(2));
return max(mx[l][range], mx[r-(1<<range)+1][range]);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &num[i]);
setTable();
while (m--) {
int l, r; scanf("%d%d", &l, &r);
printf("%d\n", query(l, r));
}
return 0;
}
Chairman Tree##
【模板】可持久化线段树/主席树
题目背景
这是个非常经典的主席树入门题——静态区间第K小
数据已经过加强,请使用主席树。同时请注意常数优化
题目描述
如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个正整数,表示这个序列各项的数字。
接下来M行每行包含三个整数l,r,k,表示查询区间[l,r]内的第k小值。
输出格式:
输出包含k行,每行1个正整数,依次表示每一次查询的结果
输入输出样例
输入样例#1:
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
输出样例#1:
6405
15770
26287
25957
26287
说明
数据范围:
对于20%的数据满足:1≤N,M≤10
对于50%的数据满足:1≤N,M≤1000
对于80%的数据满足:1≤N,M≤1e5
对于100%的数据满足:1≤N,M≤2e5
对于数列中的所有数ai,均满足-1e9≤ai≤1e9
样例数据说明:
N=5,数列长度为5,数列从第一项开始依次为[25957,6405,15770,26287,26465]
第一次查询为[2,2]区间内的第一小值,即为6405
第二次查询为[3,4]区间内的第一小值,即为15770
第三次查询为[4,5]区间内的第一小值,即为26287
第四次查询为[1,2]区间内的第二小值,即为25957
第五次查询为[4,4]区间内的第一小值,即为26287
#include <iostream>
#include <cstdio>
#include <algorithm>
#define MAX_N 200000
using namespace std;
int n, m, num[MAX_N+5], hash[MAX_N+5], tot, root[MAX_N+5], cnt;
struct pre {int id, val;} p[MAX_N+5];
bool cmp (const pre &a, const pre &b) {return a.val < b.val;}
struct node {int ls, rs, val;} tr[MAX_N*50];
void updata(int v) {tr[v].val = tr[tr[v].ls].val+tr[tr[v].rs].val;}
void build(int v, int s, int t) {
if (s == t) return; int mid = s+t>>1;
tr[v].ls = ++cnt, tr[v].rs = ++cnt;
build(tr[v].ls, s, mid), build(tr[v].rs, mid+1, t);
}
void modify(int v, int o, int s, int t, int x) {
tr[v] = tr[o]; if (s == t) {tr[v].val++; return;}
int mid = s+t>>1;
if (x <= mid) modify(tr[v].ls = ++cnt, tr[o].ls, s, mid, x);
else modify(tr[v].rs = ++cnt, tr[o].rs, mid+1, t, x);
updata(v);
}
int query(int v1, int v2, int s, int t, int k) {
if (s == t) return s;
int mid = s+t>>1, tmp = tr[tr[v2].ls].val-tr[tr[v1].ls].val;
if (k <= tmp) return query(tr[v1].ls, tr[v2].ls, s, mid, k);
else return query(tr[v1].rs, tr[v2].rs, mid+1, t, k-tmp);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) p[i].id = i, scanf("%d", &p[i].val);
sort(p+1, p+n+1, cmp);
for (int i = 1; i <= n; i++) {if (p[i].val != p[i-1].val || i == 1) hash[++tot] = p[i].val; num[p[i].id] = tot;}
root[0] = ++cnt, build(root[0], 1, tot);
for (int i = 1; i <= n; i++) root[i] = ++cnt, modify(root[i], root[i-1], 1, tot, num[i]);
while (m--) {
int l, r, k; scanf("%d%d%d", &l, &r, &k);
printf("%d\n", hash[query(root[l-1], root[r], 1, tot, k)]);
}
return 0;
}
Treap##
【模板】普通平衡树
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
求x的后继(后继定义为大于x,且最小的数)
输入输出格式
输入格式:
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)
输出格式:
对于操作3,4,5,6每行输出一个数,表示对应答案
输入输出样例
输入样例:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出样例:
106465
84185
492737
说明
时空限制:1000ms,128M
1.n的数据范围:n<=100000
2.每个数的数据范围:[-1e7,1e7]
#include <iostream>
#include <cstdio>
#include <cstdlib>
#define MAX_N 100000
using namespace std;
struct TNode {
TNode* s[2];
int val, k, size;
TNode() {}
TNode(int _val, TNode* _s) {val = _val, s[0] = s[1] = _s, k = rand(), size = 1;}
void updata() {size = s[0]->size+s[1]->size+1;}
} nil, tr[MAX_N+5], *null, *root, *cnt;
typedef TNode* P_TNode;
void init() {
srand(19260817);
nil = TNode(0, NULL), null = &nil;
null->s[0] = null->s[1] = null, null->size = 0;
cnt = tr, root = null;
}
P_TNode newnode(int val) {
*cnt = TNode(val, null);
return cnt++;
}
P_TNode merge(P_TNode a, P_TNode b) {
if (a == null) return b;
if (b == null) return a;
if (a->k > b->k) {a->s[1] = merge(a->s[1], b), a->updata(); return a;}
if (a->k <= b->k) {b->s[0] = merge(a, b->s[0]), b->updata(); return b;}
}
void split(P_TNode v, int val, P_TNode &ls, P_TNode &rs) {
if (v == null) {ls = rs = null; return;}
if (val < v->val) {rs = v; split(rs->s[0], val, ls, rs->s[0]);}
if (val >= v->val) {ls = v; split(ls->s[1], val, ls->s[1], rs);}
v->updata();
}
void insert(int val) {
P_TNode ls, rs;
split(root, val, ls, rs);
root = merge(merge(ls, newnode(val)), rs);
}
void remove(int val) {
P_TNode ls, mids, rs;
split(root, val-1, ls, rs);
split(rs, val, mids, rs);
root = merge(ls, merge(merge(mids->s[0], mids->s[1]), rs));
}
int get_rank(int val) {
P_TNode ls, rs;
split(root, val-1, ls, rs);
int ret = ls->size+1;
root = merge(ls, rs);
return ret;
}
int get_kth(P_TNode v, int k) {
if (k <= v->s[0]->size) return get_kth(v->s[0], k);
if (k > v->s[0]->size+1) return get_kth(v->s[1], k-v->s[0]->size-1);
return v->val;
}
int get_nearest(P_TNode v, int sn) {
while (v->s[sn] != null) v = v->s[sn];
return v->val;
}
int predecessor(int val) {
P_TNode ls, rs;
split(root, val-1, ls, rs);
int ret = get_nearest(ls, 1);
root = merge(ls, rs);
return ret;
}
int successor(int val) {
P_TNode ls, rs;
split(root, val, ls, rs);
int ret = get_nearest(rs, 0);
root = merge(ls, rs);
return ret;
}
int main() {
init();
int n, opt, x;
scanf("%d", &n);
while (n--) {
scanf("%d%d", &opt, &x);
if (opt == 1) insert(x);
if (opt == 2) remove(x);
if (opt == 3) printf("%d\n", get_rank(x));
if (opt == 4) printf("%d\n", get_kth(root, x));
if (opt == 5) printf("%d\n", predecessor(x));
if (opt == 6) printf("%d\n", successor(x));
}
return 0;
}
Splay##
Normal###
【模板】普通平衡树
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
求x的后继(后继定义为大于x,且最小的数)
输入输出格式
输入格式:
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)
输出格式:
对于操作3,4,5,6每行输出一个数,表示对应答案
输入输出样例
输入样例:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出样例:
106465
84185
492737
说明
时空限制:1000ms,128M
1.n的数据范围:n<=100000
2.每个数的数据范围:[-1e7,1e7]
#include <iostream>
#include <cstdio>
#define MAX_N 100000
#define INF 2147483647
using namespace std;
struct SplayNode {
SplayNode *s[2], *fa;
int val, w, size;
void updata();
} nil;
SplayNode* null = &nil;
struct Splay {
SplayNode tr[MAX_N+5];
SplayNode* root;
int cnt;
Splay();
SplayNode* newnode(int _val);
void rotate(SplayNode* v, bool sn);
void rotate(SplayNode* &v);
void splay(SplayNode* now, SplayNode* &to);
SplayNode* predecessor(int _val);
SplayNode* successor(int _val);
void insert(int _val);
void remove(int _val);
int get_rank(int _val);
int get_kth(int rank);
} BBST;
void SplayNode::updata() {
if (!s[1]) return;
size = s[0]->size+s[1]->size+w;
}
Splay::Splay() {
cnt = 0;
root = newnode(-INF);
root->s[1] = newnode(INF);
root->s[1]->fa = root;
}
SplayNode* Splay::newnode(int _val) {
cnt++;
tr[cnt].s[0] = null, tr[cnt].s[1] = null, tr[cnt].fa = null;
tr[cnt].val = _val, tr[cnt].w = tr[cnt].size = 1;
return &tr[cnt];
}
void Splay::rotate(SplayNode* v, bool sn) {
SplayNode* tmp = v->s[sn^1];
v->s[sn^1] = tmp->s[sn];
tmp->s[sn] = v;
v->s[sn^1]->fa = v;
tmp->fa = v->fa;
if (tmp->fa != null) tmp->fa->s[tmp->fa->s[1] == v] = tmp;
else root = tmp;
v->fa = tmp;
v->updata();
tmp->updata();
}
void Splay::rotate(SplayNode* &v) {
bool sn = (v->fa->s[0] == v) ? 1 : 0;
rotate(v->fa, sn);
}
void Splay::splay(SplayNode* now, SplayNode* &to) {
while (now != to) {
if (now->fa == to) {
rotate(now);
} else {
if ((now->fa->s[0] == now) == (now->fa->fa->s[0] == now->fa)) rotate(now->fa);
else rotate(now);
rotate(now);
}
}
}
SplayNode* Splay::predecessor(int _val) {
SplayNode* now = root;
SplayNode* save = root;
int maxv = -INF;
for (;;) {
if (now->val < _val && maxv < _val) maxv = now->val, save = now;
SplayNode* nxt = now->s[(_val <= now->val) ? 0 : 1];
if (nxt == null) return save;
now = nxt;
}
}
SplayNode* Splay::successor(int _val) {
SplayNode* now = root;
SplayNode* save = root;
int minv = INF;
for (;;) {
if (now->val > _val && minv > _val) minv = now->val, save = now;
SplayNode* nxt = now->s[(_val < now->val) ? 0 : 1];
if (nxt == null) return save;
now = nxt;
}
}
void Splay::insert(int _val) {
SplayNode* pre = predecessor(_val);
SplayNode* suc = successor(_val);
splay(pre, root), splay(suc, root->s[1]);
if (root->s[1]->s[0] == null) {
root->s[1]->s[0] = newnode(_val);
root->s[1]->s[0]->fa = root->s[1];
} else {
root->s[1]->s[0]->w++;
root->s[1]->s[0]->size++;
}
root->s[1]->updata();
root->updata();
}
void Splay::remove(int _val) {
SplayNode* pre = predecessor(_val);
SplayNode* suc = successor(_val);
splay(pre, root), splay(suc, root->s[1]);
root->s[1]->s[0]->w--, root->s[1]->s[0]->size--;
if (!root->s[1]->s[0]->w) root->s[1]->s[0] = null;
root->s[1]->updata();
root->updata();
}
int Splay::get_rank(int _val) {
SplayNode* pre = predecessor(_val);
SplayNode* suc = successor(_val);
splay(pre, root), splay(suc, root->s[1]);
return root->size-root->s[1]->size;
}
int Splay::get_kth(int rank) {
rank++;
SplayNode* now = root;
for (;;) {
if (rank <= now->s[0]->size) {
now = now->s[0];
} else {
rank -= now->s[0]->size;
if (rank <= now->w) return now->val;
rank -= now->w;
now = now->s[1];
}
}
}
int main() {
int n, opt, x;
scanf("%d", &n);
while (n--) {
scanf("%d%d", &opt, &x);
if (opt == 1) BBST.insert(x);
if (opt == 2) BBST.remove(x);
if (opt == 3) printf("%d\n", BBST.get_rank(x));
if (opt == 4) printf("%d\n", BBST.get_kth(x));
if (opt == 5) printf("%d\n", BBST.predecessor(x)->val);
if (opt == 6) printf("%d\n", BBST.successor(x)->val);
}
return 0;
}
Reverse###
【模板】文艺平衡树
题目背景
这是一道经典的Splay模板题——文艺平衡树。
题目描述
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间。
例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1
输入输出格式
输入格式:
第一行为n,m,n表示初始序列有n个数,这个序列依次是(1,2...n-1,n),m表示翻转操作次数
接下来m行每行两个数[l,r],数据保证 1≤l≤r≤n
输出格式:
输出一行n个数字,表示原始序列经过m次变换后的结果
输入输出样例
输入样例#1:
5 3
1 3
1 3
1 4
输出样例#1:
4 3 2 1 5
说明
n,m≤100000
#include <iostream>
#include <cstdio>
#define MAX_N 100000
using namespace std;
struct SplayNode {SplayNode *s[2], *fa; int val, size; bool rev; void updata(); void downtag();};
struct SplayTree {
SplayNode* root; SplayNode* newnode(int _val); SplayNode* build(SplayNode* v, int l, int r);
void rotate(SplayNode* v, bool sn); void splay(SplayNode* now, SplayNode* to);
SplayNode* find_kth(SplayNode* v, int k); void reverse(int l, int r); void output(SplayNode* v, int n);
} BBST;
void SplayNode::updata() {size = (s[0] == NULL ? 0 : s[0]->size)+(s[1] == NULL ? 0 : s[1]->size)+1;}
void SplayNode::downtag() {
if (!rev) return; rev = false, swap(s[0], s[1]);
if (s[0]) s[0]->rev ^= 1; if (s[1]) s[1]->rev ^= 1;
}
SplayNode* SplayTree::newnode(int _val) {
SplayNode* v = new SplayNode;
v->s[0] = v->s[1] = v->fa = NULL;
v->val = _val, v->size = 1, v->rev = false;
return v;
}
SplayNode* SplayTree::build(SplayNode* fa, int l, int r) {
if (l > r) return NULL; int mid = l+r>>1, val = mid-1;
SplayNode* v = newnode(val); v->fa = fa;
v->s[0] = build(v, l, mid-1), v->s[1] = build(v, mid+1, r);
v->updata(); return v;
}
void SplayTree::rotate(SplayNode* v, bool sn) {
SplayNode* tmp = v->fa;
tmp->s[sn^1] = v->s[sn], v->fa = tmp->fa;
if (v->s[sn]) v->s[sn]->fa = tmp;
if (tmp->fa != NULL) tmp->fa->s[tmp == tmp->fa->s[1]] = v;
v->s[sn] = tmp, tmp->fa = v, tmp->updata(), v->updata();
}
void SplayTree::splay(SplayNode* now, SplayNode* to) {
while (now->fa != to)
if (now->fa->s[0] == now) {
if (now->fa->fa != to && now->fa == now->fa->fa->s[0]) rotate(now->fa, 1);
rotate(now, 1);
} else {
if (now->fa->fa != to && now->fa == now->fa->fa->s[1]) rotate(now->fa, 0);
rotate(now, 0);
}
if (to == NULL) root = now;
}
SplayNode* SplayTree::find_kth(SplayNode* v, int k) {
v->downtag(); int size = v->s[0] == NULL ? 0 : v->s[0]->size;
if (size+1 == k) return v;
if (size >= k) return find_kth(v->s[0], k);
return find_kth(v->s[1], k-size-1);
}
void SplayTree::reverse(int l, int r) {
SplayNode *nl = find_kth(root, l), *nr = find_kth(root, r+2);
splay(nl, NULL), splay(nr, root); nr->s[0]->rev ^= 1;
}
void SplayTree::output(SplayNode* v, int n) {
if (v == NULL) return;
v->downtag(), output(v->s[0], n);
if (v->val >= 1 && v->val <= n) printf("%d ", v->val);
output(v->s[1], n);
}
int main() {
int n, m; scanf("%d%d", &n, &m); BBST.root = BBST.build(NULL, 1, n+2);
for (int i = 1, l, r; i <= m; i++) scanf("%d%d", &l, &r), BBST.reverse(l, r);
BBST.output(BBST.root, n);
return 0;
}
Tree Chain Division##
【模板】树链剖分
题目描述
如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和
输入输出格式
输入格式:
第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。
接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)
接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x
输出格式:
输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模)
输入输出样例
输入样例:
5 5 2 24
7 3 7 8 0
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
输出样例:
2
21
#include <iostream>
#include <cstdio>
#include <vector>
#define MAX_N 100000
using namespace std;
int n, m, r, p, ind;
vector <int> G[MAX_N+5];
int c[MAX_N+5];
int dep[MAX_N+5], fa[MAX_N+5], size[MAX_N+5], son[MAX_N+5];
int top[MAX_N+5], dfn[MAX_N+5], last[MAX_N+5];
int seg[(MAX_N<<2)+5], tag[(MAX_N<<2)+5];
void DFS1(int u) {
size[u] = 1;
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (v == fa[u]) continue;
dep[v] = dep[u]+1;
fa[v] = u;
DFS1(v);
size[u] += size[v];
if (!son[u] || size[son[u]] < size[v]) son[u] = v;
}
}
void DFS2(int u, int tp) {
top[u] = tp, dfn[u] = ++ind;
if (son[u]) DFS2(son[u], tp);
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (v == fa[u] || v == son[u]) continue;
DFS2(v, v);
}
last[u] = ind;
}
void updata(int v) {seg[v] = (seg[v<<1]+seg[v<<1|1])%p;}
void downtag(int v, int s, int t) {
if (!tag[v]) return;
int mid = s+t>>1;
seg[v<<1] = (seg[v<<1]+tag[v]*(mid-s+1))%p;
seg[v<<1|1] = (seg[v<<1|1]+tag[v]*(t-mid))%p;
tag[v<<1] = (tag[v<<1]+tag[v])%p;
tag[v<<1|1] = (tag[v<<1|1]+tag[v])%p;
tag[v] = 0;
}
void modify(int v, int s, int t, int l, int r, int x) {
if (s >= l && t <= r) {seg[v] = (seg[v]+x*(t-s+1))%p, tag[v] = (tag[v]+x)%p; return;}
downtag(v, s, t);
int mid = s+t>>1;
if (l <= mid) modify(v<<1, s, mid, l, r, x);
if (r >= mid+1) modify(v<<1|1, mid+1, t, l, r, x);
updata(v);
}
int query(int v, int s, int t, int l, int r) {
if (s >= l && t <= r) return seg[v];
downtag(v, s, t);
int mid = s+t>>1, ret = 0;
if (l <= mid) ret = (ret+query(v<<1, s, mid, l, r))%p;
if (r >= mid+1) ret = (ret+query(v<<1|1, mid+1, t, l, r))%p;
updata(v);
return ret;
}
void solve1(int x, int y, int z) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
modify(1, 1, n, dfn[top[x]], dfn[x], z);
x = fa[top[x]];
}
modify(1, 1, n, min(dfn[x], dfn[y]), max(dfn[x], dfn[y]), z);
}
int solve2(int x, int y) {
int ret = 0;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
ret = (ret+query(1, 1, n, dfn[top[x]], dfn[x]))%p;
x = fa[top[x]];
}
ret = (ret+query(1, 1, n, min(dfn[x], dfn[y]), max(dfn[x], dfn[y])))%p;
return ret;
}
void solve3(int x, int z) {modify(1, 1, n, dfn[x], last[x], z);}
int solve4(int x) {return query(1, 1, n, dfn[x], last[x]);}
int main() {
scanf("%d%d%d%d", &n, &m, &r, &p);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
DFS1(r);
DFS2(r, r);
for (int i = 1; i <= n; i++) modify(1, 1, n, dfn[i], dfn[i], c[i]);
while (m--) {
int opt;
scanf("%d", &opt);
if (opt == 1) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
solve1(x, y, z);
}
if (opt == 2) {
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", solve2(x, y));
}
if (opt == 3) {
int x, z;
scanf("%d%d", &x, &z);
solve3(x, z);
}
if (opt == 4) {
int x;
scanf("%d", &x);
printf("%d\n", solve4(x));
}
}
return 0;
}
String#
KMP##
【模板】KMP字符串匹配
题目描述
如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。
为了减少骗分的情况,接下来还要输出子串的前缀数组next。
输入输出格式
输入格式:
第一行为一个字符串,即为s1(仅包含大写字母)
第二行为一个字符串,即为s2(仅包含大写字母)
输出格式:
若干行,每行包含一个整数,表示s2在s1中出现的位置
接下来1行,包括length(s2)个整数,表示前缀数组next[i]的值。
输入输出样例
输入样例:
ABABABC
ABA
输出样例:
1
3
0 0 1
说明
时空限制:1000ms,128M
数据规模:
设s1长度为N,s2长度为M
对于30%的数据:N<=15,M<=5
对于70%的数据:N<=10000,M<=100
对于100%的数据:N<=1000000,M<=1000
#include <iostream>
#include <cstdio>
#include <string>
#define MAX_M 1000
using namespace std;
int next[MAX_M];
void CalcNext(string& s) {
int m = s.length();
int begin = 1, matched = 0;
while (begin+matched < m) {
if (s[begin+matched] == s[matched]) {
matched++;
next[begin+matched-1] = matched;
} else {
if (matched == 0) {
begin++;
} else {
begin += matched-next[matched-1];
matched = next[matched-1];
}
}
}
}
void KMP(string& T, string& P) {
int n = T.length(), m = P.length();
int begin = 0, matched = 0;
while (begin <= n-m) {
if (matched < m && T[begin+matched] == P[matched]) {
matched++;
if (matched == m) {
cout << begin+1 << endl;
}
} else {
if (matched == 0) {
begin++;
} else {
begin += matched-next[matched-1];
matched = next[matched-1];
}
}
}
}
int main() {
string s1, s2;
cin >> s1 >> s2;
CalcNext(s2);
KMP(s1, s2);
for (int i = 0; i < s2.length(); i++) {
cout << next[i] << " ";
}
return 0;
}
Manacher##
【模板】manacher算法
题目描述
给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.
字符串长度为n
输入输出格式
输入格式:
一行小写英文字符a,b,c...y,z组成的字符串S
输出格式:
一个整数表示答案
输入输出样例
输入样例#1:
aaa
输出样例#1:
3
说明
字符串长度len <= 11000000
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_L 11000000
using namespace std;
char s[MAX_L*2+5];
int f[MAX_L*2+5];
int manacher (char* s0) {
int len = strlen(s0);
for (int i = 0; i < len; i++) s[i*2+1] = '#', s[i*2+2] = s0[i];
s[len = len*2+1] = '#';
int pos = 0, r = 0, ret = 0;
for (int i = 1; i <= len; i++) {
f[i] = (i < r) ? min(f[2*pos-i], r-i) : 1;
while (i-f[i] >= 1 && i+f[i] <= len && s[i-f[i]] == s[i+f[i]]) f[i]++;
if (i+f[i] > r) pos = i, r = i+f[i];
ret = max(ret, f[i]-1);
}
return ret;
}
int main() {
char s0[MAX_L+5]; scanf("%s", s0);
printf("%d\n", manacher(s0));
return 0;
}
Aho-Corasick Automation##
【模板】AC自动机
题目描述
有N个由小写字母组成的模式串以及一个文本串T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串T中出现的次数最多。
输入输出格式
输入格式:
输入含多组数据。
每组数据的第一行为一个正整数N,表示共有N个模式串,1≤N≤150。
接下去N行,每行一个长度小于等于70的模式串。
下一行是一个长度小于等于1e6的文本串T。
输入结束标志为N=0。
输出格式:
对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。
输入输出样例
输入样例#1:
2
aba
bab
ababababac
6
beta
alpha
haha
delta
dede
tata
dedeltalphahahahototatalpha
0
输出样例#1:
4
aba
2
alpha
haha
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define DICNUM 26
#define MAX_LETTER 10500
#define MAX_LENGTH 1000000
using namespace std;
char P[155][75], T[MAX_LENGTH+5];
int root = 1, cnt, trie[MAX_LETTER+5][DICNUM], fail[MAX_LETTER+5], end[MAX_LETTER+5], tot[155];
void init() {
memset(P, 0, sizeof(P)), memset(T, 0, sizeof(T)), memset(tot, 0, sizeof(tot));
for (int i = 1; i <= cnt; i++) memset(trie[i], 0, sizeof(trie[i])), fail[i] = end[i] = 0; cnt = 1;
}
void insert(int id, char s[]) {
int cur = 1, len = strlen(s);
for (int i = 0; i < len; cur = trie[cur][s[i++]-'a'])
if (!trie[cur][s[i]-'a']) trie[cur][s[i]-'a'] = ++cnt;
end[cur] = id;
}
void SetFail() {
queue <int> que; que.push(root);
while (!que.empty()) {
int u = que.front(); que.pop();
for (int i = 0; i < DICNUM; i++)
if (trie[u][i]) fail[trie[u][i]] = trie[fail[u]][i], que.push(trie[u][i]);
else trie[u][i] = trie[fail[u]][i];
}
}
void query() {
int cur = root, index, len = strlen(T);
for (int i = 0; i < len; i++) {
index = T[i]-'a';
while (!trie[cur][index]) cur = fail[cur];
cur = trie[cur][index];
for (int j = cur; j; j = fail[j]) tot[end[j]]++;
}
}
int main() {
int n;
for (int i = 0; i < DICNUM; i++) trie[0][i] = root;
while (scanf("%d", &n) && n) {
init();
for (int i = 1; i <= n; i++) scanf("%s", P[i]), insert(i, P[i]);
SetFail(), scanf("%s", T), query();
int ans = 0;
for (int i = 1; i <= n; i++) ans = max(ans, tot[i]);
printf("%d\n", ans);
for (int i = 1; i <= n; i++) if (tot[i] == ans) printf("%s\n", P[i]);
}
return 0;
}
Hash Table##
【模板】字符串哈希
题目描述
如题,给定N个字符串(第i个字符串长度为Mi,字符串内包含数字、大小写字母,大小写敏感),请求出N个字符串中共有多少个不同的字符串。
输入输出格式
输入格式:
第一行包含一个整数N,为字符串的个数。
接下来N行每行包含一个字符串,为所提供的字符串。
输出格式:
输出包含一行,包含一个整数,为不同的字符串个数。
输入输出样例
输入样例:
5
abc
aaaa
abc
abcc
12345
输出样例:
4
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,Mi≈6,Mmax<=15;
对于70%的数据:N<=1000,Mi≈100,Mmax<=150
对于100%的数据:N<=10000,Mi≈1000,Mmax<=1500
样例说明:
样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。
#include <iostream>
#include <cstdio>
#include <string>
#define size 15000
using namespace std;
int n, cnt = 0;
string tmp;
string hash[size];
int calc(string& index) {
int ret = 0;
for (int i = 0; i < index.length(); i++) {
ret = (ret*256+index[i]+128)%size;
}
return ret;
}
bool search(string& index, int& pos) {
pos = calc(index);
while (hash[pos] != "" && hash[pos] != index) {
pos = (pos+1)%size;
}
if (hash[pos] == index) {
return true;
} else {
return false;
}
}
int insert(string& index) {
int pos;
if (search(index, pos)) {
return 0;
} else {
hash[pos] = index;
return 1;
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> tmp;
cnt += insert(tmp);
}
cout << cnt << endl;
return 0;
}
Suffix Array##
【模板】后缀排序
题目背景
这是一道模板题。
题目描述
读入一个长度为n的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为1到n。
输入输出格式
输入格式:
一行一个长度为n的仅包含大小写英文字母或数字的字符串。
输出格式:
一行,共n个整数,表示答案。
输入输出样例
输入样例#1:
ababa
输出样例#1:
5 3 1 4 2
说明
n<=1e6
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_N 1000000
using namespace std;
int n; char ch[MAX_N+5];
int s[MAX_N+5], sa[MAX_N+5], tx[MAX_N+5], ty[MAX_N+5], cnt[MAX_N+5], rank[MAX_N+5];
int trans(char c) {
if (c >= '0' && c <= '9') return c-'0'+1;
if (c >= 'A' && c <= 'Z') return c-'A'+11;
if (c >= 'a' && c <= 'z') return c-'a'+37;
}
void getSA() {
int *x = tx, *y = ty; int DICNUM = 63;
for (int i = 1; i <= n; i++) cnt[x[i] = s[i]]++;
for (int i = 2; i <= DICNUM; i++) cnt[i] += cnt[i-1];
for (int i = n; i; i--) sa[cnt[x[i]]--] = i;
for (int h = 1; h <= n; h <<= 1) {
int c = 0;
for (int i = n-h+1; i <= n; i++) y[++c] = i;
for (int i = 1; i <= n; i++) if (sa[i] > h) y[++c] = sa[i]-h;
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++) cnt[x[i]]++;
for (int i = 2; i <= DICNUM; i++) cnt[i] += cnt[i-1];
for (int i = n; i; i--) sa[cnt[x[y[i]]]--] = y[i];
swap(x, y), c = 0, x[sa[1]] = ++c;
for (int i = 2; i <= n; i++) x[sa[i]] = (y[sa[i]] == y[sa[i-1]] && y[sa[i]+h] == y[sa[i-1]+h]) ? c : ++c;
DICNUM = c; if (c == n) break;
}
}
int main() {
scanf("%s", ch); n = strlen(ch);
for (int i = 0; i < n; i++) s[i+1] = trans(ch[i]);
getSA();
printf("%d", sa[1]); for (int i = 2; i <= n; i++) printf(" %d", sa[i]);
return 0;
}
Suffix Automation##
【模板】后缀自动机
题目描述
给定一个只包含小写字母的字符串S,请你求出S的所有出现次数不为1的子串的出现次数乘上该子串长度的最大值。
输入输出格式
输入格式:
一行一个仅包含小写字母的字符串S
输出格式:
一个整数,为所求答案
输入输出样例
输入样例#1:
abab
输出样例#1:
4
说明
对于10%的数据,|S|<=1000
对于100%的数据,|S|<=1e6
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX_N 1000000
using namespace std;
typedef long long lnt;
struct node {int ch[26], par, len;} SAM[MAX_N*2+500];
int sz, root, last, cnt[MAX_N*2+500], dfn[MAX_N*2+500], f[MAX_N*2+500];
int newnode(int _len) {SAM[++sz].len = _len; return sz;}
void init() {sz = 0, root = last = newnode(0);}
void extend(int c) {
int p = last, np = newnode(SAM[p].len+1); last = np, f[np] = 1;
for (; p && !SAM[p].ch[c]; p = SAM[p].par) SAM[p].ch[c] = np;
if (!p) SAM[np].par = root;
else {
int q = SAM[p].ch[c];
if (SAM[q].len == SAM[p].len+1) SAM[np].par = q;
else {
int nq = newnode(SAM[p].len+1);
memcpy(SAM[nq].ch, SAM[q].ch, sizeof(SAM[q].ch));
SAM[nq].par = SAM[q].par, SAM[q].par = SAM[np].par = nq;
for (; p && SAM[p].ch[c] == q; p = SAM[p].par) SAM[p].ch[c] = nq;
}
}
}
int main() {
char s[MAX_N+5]; init(), scanf("%s", s); int l = strlen(s);
for (int i = 0; i < l; i++) extend(s[i]-'a');
for (int i = 1; i <= sz; i++) cnt[SAM[i].len]++;
for (int i = 1; i <= l; i++) cnt[i] += cnt[i-1];
for (int i = 1; i <= sz; i++) dfn[cnt[SAM[i].len]--] = i;
for (int i = 1; i <= sz; i++) cout << dfn[i] << " "; cout << endl;
lnt ans = 0;
for (int i = sz; i >= 1; i--) {
int p = dfn[i]; f[SAM[p].par] += f[p];
if (f[p] > 1) ans = max(ans, (lnt)f[p]*SAM[p].len);
}
printf("%lld", ans);
return 0;
}