zoukankan      html  css  js  c++  java
  • 洛谷模板汇总

    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
    ");
    		else	printf("N0
    ");
    	}
    	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
    ", 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
    ", 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
    ", 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
    ");
    			else	printf("%d
    ", 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
    ", 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
    ", 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
    ", get_rank(x));
            if (opt == 4)    printf("%d
    ", get_kth(root, x));
            if (opt == 5)    printf("%d
    ", predecessor(x));
            if (opt == 6)    printf("%d
    ", 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
    ", BBST.get_rank(x));
    		if (opt == 4)	printf("%d
    ", BBST.get_kth(x));
    		if (opt == 5)	printf("%d
    ", BBST.predecessor(x)->val);
    		if (opt == 6)	printf("%d
    ", 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
    ", 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
    ", 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
    ", 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
    ", ans);
    		for (int i = 1; i <= n; i++)	if (tot[i] == ans)	printf("%s
    ", 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;
    }
    
  • 相关阅读:
    Vagrant 扩大磁盘根目录
    阿里云 轻量应用服务器 vnc 远程桌面连接
    图片加水印C#源代码
    Asp.net网站Pdf加水印C#源代码
    [FAQ] uni-app 如何让页面不展示返回箭头图标
    [PHP] composer, PHP Fatal error: Allowed memory size of xx bytes exhausted
    [FE] uni-app 导航栏开发指南
    [FE] uni-app 动态改变 navigationBarTitleText 导航标题
    [FE] yarn, npm 切换镜像源
    [FAQ] Phpstorm 代码提示功能失效问题
  • 原文地址:https://www.cnblogs.com/AzraelDeath/p/7602748.html
Copyright © 2011-2022 走看看