洛谷模板汇总

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;
}

【模板】三分法

题目描述
如题,给出一个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;
}
posted @ 2017-09-27 17:05  Azrael_Death  阅读(444)  评论(0编辑  收藏  举报