洛谷模板汇总

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<=10^4,m<=10^5,|点权|<=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;
}

转载于:https://www.cnblogs.com/AzraelDeath/p/7602748.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值