【复习】CSP2021-模板

一、dp

1. 01 背包

P1048 [NOIP2005 普及组] 采药

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXM = 105;
const int MAXT = 1005;

int v[MAXM], w[MAXM];
int dp[MAXT];

int main()
{
	int t, m;
	scanf("%d%d", &t, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", v + i, w + i);
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = t; j >= v[i]; j--)
		{
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	printf("%d\n", dp[t]);
	return 0;
}

2. 完全背包

P1616 疯狂的采药

tips:开 long long

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

const int MAXM = 1e4 + 5;
const int MAXT = 1e7 + 5;

int v[MAXM], w[MAXM];
int dp[MAXT];

signed main()
{
	int t, m;
	scanf("%lld%lld", &t, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf("%lld%lld", v + i, w + i);
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = v[i]; j <= t; j++)
		{
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	printf("%lld\n", dp[t]);
	return 0;
}

3. 多重背包(二进制优化)

P1776 宝物筛选

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e5 + 5;
const int MAXT = 4e4 + 5;

int v[MAXN], w[MAXN];
int dp[MAXT];

int main()
{
	int n, t, tot = 0;
	scanf("%d%d", &n, &t);
	for (int i = 1; i <= n; i++)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		for (int j = 1; j <= c; j <<= 1)
		{
			c -= j;
			w[++tot] = a * j;
			v[tot] = b * j;
		}
		if (c)
		{
			w[++tot] = a * c;
			v[tot] = b * c;
		}
	}
	for (int i = 1; i <= tot; i++)
	{
		for (int j = t; j >= v[i]; j--)
		{
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	printf("%d\n", dp[t]);
	return 0;
}

4. 区间 dp(环形类问题)

P1880 [NOI1995] 石子合并

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 205;

int a[MAXN], sum[MAXN], dp1[MAXN][MAXN], dp2[MAXN][MAXN];

int main()
{
	memset(dp1, 0x3f, sizeof(dp1));
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", a + i);
		a[i + n] = a[i];
	}
	for (int i = 1; i <= (n << 1); i++)
	{
		sum[i] = sum[i - 1] + a[i];
		dp1[i][i] = 0;
	}
	for (int len = 2; len <= n; len++)
	{
		for (int i = 1; i + len - 1 <= (n << 1); i++)
		{
			int j = i + len - 1;
			dp1[i][j] = 0x3f3f3f3f;
			for (int k = i; k < j; k++)
			{
				dp1[i][j] = min(dp1[i][j], dp1[i][k] + dp1[k + 1][j] + sum[j] - sum[i - 1]);
				dp2[i][j] = max(dp2[i][j], dp2[i][k] + dp2[k + 1][j] + sum[j] - sum[i - 1]);
			}
		}
	}
	int ans1 = 0x3f3f3f3f, ans2 = 0;
	for (int i = 1; i <= n; i++)
	{
		ans1 = min(ans1, dp1[i][i + n - 1]);
		ans2 = max(ans2, dp2[i][i + n - 1]);
	}
	printf("%d\n%d\n", ans1, ans2);
	return 0;
}

二、图论

1. 最短路

(1) dijkstra

P4779 【模板】单源最短路径(标准版)

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;

const int MAXN = 1e5 + 5;
const int MAXM = 2e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, dis, nxt;
}e[MAXM];

void add(int u, int v, int w)
{
	e[++cnt] = edge{v, w, head[u]};
	head[u] = cnt;
}

struct que
{
	int pos, dis;
	bool operator <(const que &x)const
	{
		return x.dis < dis;
	}
};

int dis[MAXN];
bool vis[MAXN];
priority_queue<que> pq;

void dijkstra(int s)
{
	memset(dis, 0x3f, sizeof(dis));
	dis[s] = 0;
	pq.push(que{s, 0});
	while (!pq.empty())
	{
		int u = pq.top().pos;
		pq.pop();
		if (vis[u])
		{
			continue;
		}
		vis[u] = true;
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to, w = e[i].dis;
			if (dis[v] > dis[u] + w)
			{
				dis[v] = dis[u] + w;
				pq.push(que{v, dis[v]});
			}
		}
	}
}

int main()
{
	int n, m, s;
	scanf("%d%d%d", &n, &m, &s);
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w);
	}
	dijkstra(s);
	for (int i = 1; i <= n; i++)
	{
		printf("%d ", dis[i]);
	}
	return 0;
}

(2) spfa

P3371 【模板】单源最短路径(弱化版)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int MAXN = 1e4 + 5;
const int MAXM = 5e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, dis, nxt;
}e[MAXM];

void add(int u, int v, int w)
{
	e[++cnt] = edge{v, w, head[u]};
	head[u] = cnt;
}

int dis[MAXN];
bool vis[MAXN];
queue<int> q;

void spfa(int s)
{
	memset(dis, 0x3f, sizeof(dis));
	dis[s] = 0;
	vis[s] = true;
	q.push(s);
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		vis[u] = false;
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to, w = e[i].dis;
			if (dis[v] > dis[u] + w)
			{
				dis[v] = dis[u] + w;
				if (!vis[v])
				{
					vis[v] = true;
					q.push(v);
				}
			}
		}
	}
}

int main()
{
	int n, m, s;
	scanf("%d%d%d", &n, &m, &s);
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w);
	}
	spfa(s);
	for (int i = 1; i <= n; i++)
	{
		if (dis[i] == 0x3f3f3f3f)
		{
			printf("2147483647 ");
		}
		else
		{
			printf("%d ", dis[i]);
		}
	}
	return 0;
}

(3) floyd

P2888 [USACO07NOV]Cow Hurdles S

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 305;

int n, m, t;
int dp[MAXN][MAXN];

void floyd()
{
	for (int k = 1; k <= n; k++)
	{
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				dp[i][j] = min(dp[i][j], max(dp[i][k], dp[k][j]));
			}
		}
	}
}

int main()
{
	memset(dp, 0x3f, sizeof(dp));
	scanf("%d%d%d", &n, &m, &t);
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		dp[u][v] = min(dp[u][v], w);
	}
	floyd();
	while (t--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		if (dp[a][b] == 0x3f3f3f3f)
		{
			puts("-1");
		}
		else
		{
			printf("%d\n", dp[a][b]);
		}
	}
	return 0;
}

2. 负环

P3385 【模板】负环

// measure it.
#include <cstdio>
#include <cstring>
#include <queue>
#define Debug(x) cout << #x << "=" << x << endl
typedef long long ll;
using namespace std;

const int MAXN = 2e3 + 5;
const int MAXM = 3e3 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, dis, nxt;
}e[MAXM << 1];

void Add(int u, int v, int w)
{
	e[++cnt] = edge{v, w, head[u]};
	head[u] = cnt;
}

int t, n, m;
int dis[MAXN], inq[MAXN];
bool vis[MAXN];

bool SPFA()
{
	dis[1] = 0, inq[1] = 1;
	queue<int> q;
	q.push(1);
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		vis[u] = false;
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to, w = e[i].dis;
			if (dis[v] > dis[u] + w)
			{
				dis[v] = dis[u] + w;
				if (!vis[v])
				{
					if (++inq[v] > n)
						return false;
					vis[v] = true;
					q.push(v);
				}
			}
		}
	}
	return true;
}

void Init()
{
	cnt = 0;
	memset(head, 0, sizeof(head));
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, false, sizeof(vis));
	memset(inq, 0, sizeof(inq));
}

int main()
{
	scanf("%d", &t);
	while (t--)
	{
		Init();
		scanf("%d%d", &n, &m);
		for (int i = 1, u, v, w; i <= m; i++)
		{
			scanf("%d%d%d", &u, &v, &w);
			Add(u, v, w);
			if (w >= 0)
				Add(v, u, w);
		}
		if (SPFA())
			puts("NO");
		else
			puts("YES");
	}
	return 0;
}

3. 差分约束

P5960 【模板】差分约束算法

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int MAXN = 5e3 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, dis, nxt;
}e[MAXN << 1];

void add(int u, int v, int w)
{
	e[++cnt] = edge{v, w, head[u]};
	head[u] = cnt;
}

int dis[MAXN], inq[MAXN];
bool vis[MAXN];
queue<int> q;

int n, m;

bool spfa()
{
	memset(dis, 0x3f, sizeof(dis));
	dis[0] = 0;
	vis[0] = true;
	q.push(0);
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		vis[u] = false;
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to, w = e[i].dis;
			if (dis[v] > dis[u] + w)
			{
				dis[v] = dis[u] + w;
				if (!vis[v])
				{
					if (++inq[v] > n)
					{
						return true;
					}
					vis[v] = true;
					q.push(v);
				}
			}
		}
	}
	return false;
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		add(0, i, 0);
	}
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		add(v, u, w);
	}
	if (spfa())
	{
		puts("NO");
	}
	else
	{
		for (int i = 1; i <= n; i++)
		{
			printf("%d ", dis[i]);
		}
	}
	return 0;
}

4. 最小生成树

(1) kruskal

P3366 【模板】最小生成树

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 5005;
const int MAXM = 2e5 + 5;

struct edge
{
	int from, to, dis;
}e[MAXM];

bool cmp(edge x, edge y)
{
	return x.dis < y.dis;
}

int n, m;
int fa[MAXN], dep[MAXN];

void init()
{
	for (int i = 1; i <= n; i++)
	{
		fa[i] = i;
		dep[i] = 1;
	}
}

int find(int x)
{
	if (x == fa[x])
	{
		return x;
	}
	return fa[x] = find(fa[x]);
}

int tot, ans;

void merge(int x, int y, int w)
{
	x = find(x), y = find(y);
	if (x != y)
	{
		tot++;
		ans += w;
		if (dep[x] > dep[y])
		{
			fa[y] = x;
		}
		else
		{
			fa[x] = y;
			if (dep[x] == dep[y])
			{
				dep[y]++;
			}
		}
	}
}

void kruskal()
{
	sort(e + 1, e + m + 1, cmp);
	for (int i = 1; i <= m; i++)
	{
		merge(e[i].from, e[i].to, e[i].dis);
		if (tot == n - 1)
		{
			break;
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	init();
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d", &e[i].from, &e[i].to, &e[i].dis);
	}
	kruskal();
	if (tot < n - 1)
	{
		puts("orz");
		return 0;
	}
	printf("%d\n", ans);
	return 0;
}

(2) prim

P3366 【模板】最小生成树

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int MAXN = 5005;
const int MAXM = 2e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, dis, nxt;
}e[MAXM << 1];

void add(int u, int v, int w)
{
	e[++cnt] = edge{v, w, head[u]};
	head[u] = cnt;
}

bool vis[MAXN];

struct que
{
	int pos, dis;
	bool operator <(const que &x)const
	{
		return x.dis < dis;
	}
};

priority_queue<que> pq;
int n, m, tot, ans;

void prim()
{
	pq.push(que{1, 0});
	while (!pq.empty())
	{
		int u = pq.top().pos, w = pq.top().dis;
		pq.pop();
		if (vis[u])
		{
			continue;
		}
		vis[u] = true;
		ans += w;
		if (++tot == n)
		{
			return;
		}
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to, w = e[i].dis;
			if (!vis[v])
			{
				pq.push(que{v, w});
			}
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w);
		add(v, u, w);
	}
	prim();
	if (tot < n)
	{
		puts("orz");
		return 0;
	}
	printf("%d\n", ans);
	return 0;
}

5. 强连通分量

(1) 模板

B3609 [图论与代数结构 701] 强连通分量

#include <iostream>
#include <cstdio>
#include <stack>
#include <vector>
#include <algorithm>
using namespace std;

const int MAXN = 1e4 + 5;
const int MAXM = 1e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int Time, tot;
int dfn[MAXN], low[MAXN], id[MAXN];
bool ins[MAXN];
stack<int> st;
vector<int> scc[MAXN];

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time;
	st.push(u);
	ins[u] = true;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (ins[v])
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u])
	{
		tot++;
		int v = 0;
		while (v != u)
		{
			v = st.top();
			st.pop();
			ins[v] = false;
			id[v] = tot;
			scc[tot].push_back(v);
		}
	}
}

bool vis[MAXN];

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(i);
		}
	}
	printf("%d\n", tot);
	for (int i = 1; i <= n; i++)
	{
		int x = id[i];
		if (vis[x])
		{
			continue;
		}
		vis[x] = true;
		sort(scc[x].begin(), scc[x].end());
		for (int j = 0; j < scc[x].size(); j++)
		{
			printf("%d ", scc[x][j]);
		}
		puts("");
	}
	return 0;
}

(2) 缩点

P3387 【模板】缩点

#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <queue>
using namespace std;

const int MAXN = 1e4 + 5;
const int MAXM = 1e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int cnt_scc;
int head_scc[MAXN];

struct edge_scc
{
	int to, nxt;
}e_scc[MAXM];

void add_scc(int u, int v)
{
	e_scc[++cnt_scc] = edge_scc{v, head_scc[u]};
	head_scc[u] = cnt_scc;
}

int Time, tot;
int dfn[MAXN], low[MAXN], sum[MAXN];
bool ins[MAXN];
stack<int> st;

int cntn, id[MAXN];

int a[MAXN];

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time;
	st.push(u);
	ins[u] = true;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (ins[v])
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u])
	{
		int v = 0;
		tot++;
		while (v != u)
		{
			v = st.top();
			st.pop();
			sum[tot] += a[v];
			id[v] = tot;
			ins[v] = false;
		}
	}
}

int n, m;
int in[MAXN], dp[MAXN];
queue<int> q;

void topo()
{
	for (int i = 1; i <= tot; i++)
	{
		if (!in[i])
		{
			q.push(i);
			dp[i] = sum[i];
		}
	}
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		for (int i = head_scc[u]; i; i = e_scc[i].nxt)
		{
			int v = e_scc[i].to;
			dp[v] = max(dp[v], dp[u] + sum[v]);
			if (!--in[v])
			{
				q.push(v);
			}
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", a + i);
	}
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(i);
		}
	}
	for (int u = 1; u <= n; u++)
	{
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (id[u] != id[v])
			{
				add_scc(id[u], id[v]);
				in[id[v]]++;
			}
		}
	}
	topo();
	int ans = 0;
	for (int i = 1; i <= tot; i++)
	{
		ans = max(ans, dp[i]);
	}
	printf("%d\n", ans);
	return 0;
}

(3) 2-SAT

P4782 【模板】2-SAT 问题

tips:数组全部要开 \(2\) 倍。

#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;

const int MAXN = 1e6 + 5;

int cnt;
int head[MAXN << 1];

struct edge
{
	int to, nxt;
}e[MAXN << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int Time, tot;
int dfn[MAXN << 1], low[MAXN << 1], id[MAXN << 1];
bool ins[MAXN << 1];
stack<int> st;

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time;
	st.push(u);
	ins[u] = true;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (ins[v])
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u])
	{
		tot++;
		int v = 0;
		while (v != u)
		{
			v = st.top();
			st.pop();
			ins[v] = false;
			id[v] = tot;
		}
	}
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	while (m--)
	{
		int i, a, j, b;
		scanf("%d%d%d%d", &i, &a, &j, &b);
		add(i + a * n, j + (1 - b) * n);
		add(j + b * n, i + (1 - a) * n);
	}
	for (int i = 1; i <= (n << 1); i++)
	{
		if (!dfn[i])
		{
			tarjan(i);
		}
	}
	for (int i = 1; i <= n; i++)
	{
		if (id[i] == id[i + n])
		{
			puts("IMPOSSIBLE");
			return 0;
		}
	}
	puts("POSSIBLE");
	for (int i = 1; i <= n; i++)
	{
		printf("%d ", id[i] < id[i + n]);
	}
	return 0;
}

6. 割点与桥

(1) 割点

P3388 【模板】割点(割顶)

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 2e4 + 5;
const int MAXM = 1e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int Time, rt, tot;
int dfn[MAXN], low[MAXN];
bool cut[MAXN];

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time;
	int flag = 0;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if (dfn[u] <= low[v])
			{
				if (u != rt || ++flag > 1)
				{
					if (!cut[u])
					{
						cut[u] = true;
						tot++;
					}
				}
			}
		}
		else
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(rt = i);
		}
	}
	printf("%d\n", tot);
	for (int i = 1; i <= n; i++)
	{
		if (cut[i])
		{
			printf("%d ", i);
		}
	}
	return 0;
}

(2) 桥

P1656 炸铁路

tips:

1. \(cnt\) 要初始化为 \(1\)

2. 要写成 i != (in_edge ^ 1) 而不是 i != in_edge ^ 1

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 155;
const int MAXM = 5005;

int cnt = 1;
int head[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int tot;

struct bridge
{
	int u, v;
}ans[MAXM];

void add_bridge(int u, int v)
{
	ans[++tot] = bridge{min(u, v), max(u, v)};
}

bool cmp(bridge x, bridge y)
{
	if (x.u == y.u)
	{
		return x.v < y.v;
	}
	return x.u < y.u;
}

int Time;
int dfn[MAXN], low[MAXN];

void tarjan(int u, int in_edge)
{
	dfn[u] = low[u] = ++Time;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v, i);
			low[u] = min(low[u], low[v]);
			if (dfn[u] < low[v])
			{
				add_bridge(u, v);
			}
		}
		else if (i != (in_edge ^ 1))
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(i, 0);
		}
	}
	sort(ans + 1, ans + tot + 1, cmp);
	for (int i = 1; i <= tot; i++)
	{
		printf("%d %d\n", ans[i].u, ans[i].v);
	}
	return 0;
}

7. 双连通分量

(1) 点双

T103492 【模板】点双连通分量

#include <iostream>
#include <cstdio>
#include <stack>
#include <vector>
using namespace std;

const int MAXN = 5e4 + 5;
const int MAXM = 3e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int Time, rt, tot;
int dfn[MAXN], low[MAXN];
stack<int> st;
vector<int> dcc[MAXN];

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time;
	if (u == rt && !head[u])
	{
		dcc[++tot].push_back(u);
		return;
	}
	st.push(u);
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if (dfn[u] <= low[v])
			{
				tot++;
				int x = 0;
				while (x != v)
				{
					x = st.top();
					st.pop();
					dcc[tot].push_back(x);
				}
				dcc[tot].push_back(u);
			}
		}
		else
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(rt = i);
		}
	}
	for (int i = 1; i <= tot; i++)
	{
		for (int j = 0; j < dcc[i].size(); j++)
		{
			printf("%d ", dcc[i][j]);
		}
		puts("");
	}
	return 0;
}

(2) 边双

T103489 【模板】边双连通分量

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 5e4 + 5;
const int MAXM = 3e5 + 5;

int cnt = 1;
int head[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int Time;
int dfn[MAXN], low[MAXN];
bool bridge[MAXM << 1];

void tarjan(int u, int in_edge)
{
	dfn[u] = low[u] = ++Time;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v, i);
			low[u] = min(low[u], low[v]);
			if (dfn[u] < low[v])
			{
				bridge[i] = bridge[i ^ 1] = true;
			}
		}
		else if (i != (in_edge ^ 1))
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
}

bool vis[MAXN];

void dfs(int u)
{
	vis[u] = true;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (vis[v] || bridge[i])
		{
			continue;
		}
		dfs(v);
	}
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(i, 0);
		}
	}
	int tot = 0;
	for (int i = 1; i <= n; i++)
	{
		if (!vis[i])
		{
			tot++;
			dfs(i);
		}
	}
	printf("%d\n", tot);
	return 0;
}

8. 欧拉图

P7771 【模板】欧拉路径

#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;

const int MAXN = 1e5 + 5;
const int MAXM = 2e5 + 5;

vector<int> G[MAXN];
int in[MAXN], out[MAXN], st[MAXN];
stack<int> ans;

void dfs(int u)
{
	for (int i = st[u]; i < G[u].size(); i = st[u])
	{
		st[u] = i + 1;
		dfs(G[u][i]);
	}
	ans.push(u);
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		G[u].push_back(v);
		out[u]++, in[v]++;
	}
	int s = 0, cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		sort(G[i].begin(), G[i].end());
		if (in[i] != out[i])
		{
			cnt++;
			if (in[i] + 1 == out[i])
			{
				s = i;
			}
		}
	}
	if (cnt == 0)
	{
		s = 1;
	}
	else if (cnt != 2)
	{
		puts("No");
		return 0;
	}
	dfs(s);
	while (!ans.empty())
	{
		printf("%d ", ans.top());
		ans.pop();
	}
	return 0;
}

9. LCA

P3379 【模板】最近公共祖先(LCA)

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 5e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, nxt;
}e[MAXN << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int fa[MAXN][20], dep[MAXN];

void dfs(int u, int father)
{
	fa[u][0] = father;
	dep[u] = dep[father] + 1;
	for (int i = 1; i <= 19; i++)
	{
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	}
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == father)
		{
			continue;
		}
		dfs(v, u);
	}
}

int lca(int x, int y)
{
	if (dep[x] < dep[y])
	{
		swap(x, y);
	}
	for (int i = 19; i >= 0; i--)
	{
		if (dep[fa[x][i]] >= dep[y])
		{
			x = fa[x][i];
		}
	}
	if (x == y)
	{
		return x;
	}
	for (int i = 19; i >= 0; i--)
	{
		if (fa[x][i] != fa[y][i])
		{
			x = fa[x][i], y = fa[y][i];
		}
	}
	return fa[x][0];
}

int main()
{
	int n, m, s;
	scanf("%d%d%d", &n, &m, &s);
	for (int i = 1; i < n; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	dfs(s, 0);
	while (m--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		printf("%d\n", lca(a, b));
	}
	return 0;
}

三、数据结构

1. 堆

P3378 【模板】堆

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e6 + 5;

int tot;
int hp[MAXN];

void push(int x)
{
	hp[++tot] = x;
	int pos = tot;
	while (tot)
	{
		int fa = pos >> 1;
		if (hp[pos] < hp[fa])
		{
			swap(hp[pos], hp[fa]);
		}
		else
		{
			break;
		}
		pos = fa;
	}
}

void pop()
{
	swap(hp[1], hp[tot--]);
	int pos = 1;
	while ((pos << 1) <= tot)
	{
		int lson = pos << 1, rson = pos << 1 | 1;
		if (rson <= tot && hp[rson] < hp[lson])
		{
			lson++;
		}
		if (hp[lson] < hp[pos])
		{
			swap(hp[lson], hp[pos]);
		}
		else
		{
			break;
		}
		pos = lson;
	}
}

int main()
{
	int n;
	scanf("%d", &n);
	while (n--)
	{
		int op, x;
		scanf("%d", &op);
		if (op == 1)
		{
			scanf("%d", &x);
			push(x);
		}
		else if (op == 2)
		{
			printf("%d\n", hp[1]);
		}
		else
		{
			pop();
		}
	}
	return 0;
}

2. 并查集(路径压缩+按秩合并)

P3367 【模板】并查集

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e4 + 5;

int n, m;
int fa[MAXN], dep[MAXN];

void init()
{
	for (int i = 1; i <= n; i++)
	{
		fa[i] = i;
	}
}

int find(int x)
{
	if (x == fa[x])
	{
		return x;
	}
	return fa[x] = find(fa[x]);
}

void merge(int x, int y)
{
	x = find(x), y = find(y);
	if (x != y)
	{
		if (dep[x] > dep[y])
		{
			fa[y] = x;
		}
		else
		{
			fa[x] = y;
			if (dep[x] == dep[y])
			{
				dep[y]++;
			}
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	init();
	while (m--)
	{
		int z, x, y;
		scanf("%d%d%d", &z, &x, &y);
		if (z == 1)
		{
			merge(x, y);
		}
		else
		{
			if (find(x) == find(y))
			{
				puts("Y");
			}
			else
			{
				puts("N");
			}
		}
	}
	return 0;
}

3. 线段树

P3372 【模板】线段树 1

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

const int MAXN = 1e5 + 5;

int a[MAXN];

#define lson pos << 1
#define rson pos << 1 | 1

struct tree
{
	int l, r, siz, val, tag = 0;
}t[MAXN << 2];

void pushup(int pos)
{
	t[pos].val = t[lson].val + t[rson].val;
}

void pushdown(int pos)
{
	if (t[pos].tag)
	{
		t[lson].val += t[pos].tag * t[lson].siz;
		t[rson].val += t[pos].tag * t[rson].siz;
		t[lson].tag += t[pos].tag;
		t[rson].tag += t[pos].tag;
		t[pos].tag = 0;
	}
}

void build(int pos, int l, int r)
{
	t[pos].l = l, t[pos].r = r, t[pos].siz = r - l + 1;
	if (l == r)
	{
		t[pos].val = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(lson, l, mid);
	build(rson, mid + 1, r);
	pushup(pos);
}

void update(int pos, int L, int R, int k)
{
	int l = t[pos].l, r = t[pos].r;
	if (L <= l && r <= R)
	{
		t[pos].val += k * t[pos].siz;
		t[pos].tag += k;
		return;
	}
	pushdown(pos);
	int mid = (l + r) >> 1;
	if (L <= mid)
	{
		update(lson, L, R, k);
	}
	if (R > mid)
	{
		update(rson, L, R, k);
	}
	pushup(pos);
}

int query(int pos, int L, int R)
{
	int l = t[pos].l, r = t[pos].r;
	if (L <= l && r <= R)
	{
		return t[pos].val;
	}
	pushdown(pos);
	int mid = (l + r) >> 1, res = 0;
	if (L <= mid)
	{
		res = query(lson, L, R);
	}
	if (R > mid)
	{
		res += query(rson, L, R);
	}
	return res;
}

signed main()
{
	int n, m;
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", a + i);
	}
	build(1, 1, n);
	while (m--)
	{
		int op, x, y, k;
		scanf("%lld%lld%lld", &op, &x, &y);
		if (op == 1)
		{
			scanf("%lld", &k);
			update(1, x, y, k);
		}
		else
		{
			printf("%lld\n", query(1, x, y));
		}
	}
	return 0;
}

4. 树状数组

(1) 一维单修区查

P3374 【模板】树状数组 1

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 5e5 + 5;

int n, m;
int c[MAXN];

int lowbit(int x)
{
	return x & -x;
}

void update(int x, int k)
{
	for (int i = x; i <= n; i += lowbit(i))
	{
		c[i] += k;
	}
}

int query(int x)
{
	int res = 0;
	for (int i = x; i; i -= lowbit(i))
	{
		res += c[i];
	}
	return res;
}

int query_range(int l, int r)
{
	return query(r) - query(l - 1);
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		int x;
		scanf("%d", &x);
		update(i, x);
	}
	while (m--)
	{
		int op, x, y;
		scanf("%d%d%d", &op, &x, &y);
		if (op == 1)
		{
			update(x, y);
		}
		else
		{
			printf("%d\n", query_range(x, y));
		}
	}
	return 0;
}

(2) 一维区修单查

P3368 【模板】树状数组 2

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 5e5 + 5;

int n, m;
int c[MAXN];

int lowbit(int x)
{
	return x & -x;
}

void update(int x, int k)
{
	for (int i = x; i <= n; i += lowbit(i))
	{
		c[i] += k;
	}
}

void update_range(int l, int r, int k)
{
	update(l, k);
	update(r + 1, -k);
}

int query(int x)
{
	int res = 0;
	for (int i = x; i; i -= lowbit(i))
	{
		res += c[i];
	}
	return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	int now, lst = 0;
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &now);
		update(i, now - lst);
		lst = now;
	}
	while (m--)
	{
		int op, x, y, k;
		scanf("%d%d", &op, &x);
		if (op == 1)
		{
			scanf("%d%d", &y, &k);
			update_range(x, y, k);
		}
		else
		{
			printf("%d\n", query(x));
		}
	}
	return 0;
}

(3) 二维区修区查

P4514 上帝造题的七分钟

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 2050;

int n, m;
int c1[MAXN][MAXN], c2[MAXN][MAXN], c3[MAXN][MAXN], c4[MAXN][MAXN];

int lowbit(int x)
{
	return x & -x;
}

void update(int x, int y, int k)
{
	for (int i = x; i <= n; i += lowbit(i))
	{
		for (int j = y; j <= m; j += lowbit(j))
		{
			c1[i][j] += k;
			c2[i][j] += k * x;
			c3[i][j] += k * y;
			c4[i][j] += k * x * y;
		}
	}
}

void update_range(int a, int b, int c, int d, int k)
{
	update(a, b, k);
	update(a, d + 1, -k);
	update(c + 1, b, -k);
	update(c + 1, d + 1, k);
}

int query(int x, int y)
{
	int res = 0;
	for (int i = x; i; i -= lowbit(i))
	{
		for (int j = y; j; j -= lowbit(j))
		{
			res += c1[i][j] * (x + 1) * (y + 1) - c2[i][j] * (y + 1) - c3[i][j] * (x + 1) + c4[i][j];
		}
	}
	return res;
}

int query_range(int a, int b, int c, int d)
{
	return query(c, d) - query(a - 1, d) - query(c, b - 1) + query(a - 1, b - 1);
}

int main()
{
	char op[3];
	int a, b, c, d, k;
	scanf("%s%d%d", op, &n, &m);
	while (~scanf("%s%d%d%d%d", op, &a, &b, &c, &d))
	{
		if (op[0] == 'L')
		{
			scanf("%d", &k);
			update_range(a, b, c, d, k);
		}
		else
		{
			printf("%d\n", query_range(a, b, c, d));
		}
	}
	return 0;
}

5. 轻重链剖分

P3384 【模板】轻重链剖分/树链剖分

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e5 + 5;

int cnt;
int head[MAXN];

struct edge
{
	int to, nxt;
}e[MAXN << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

int Time;
int fa[MAXN], dep[MAXN], siz[MAXN], son[MAXN], dfn[MAXN], top[MAXN];

void dfs1(int u, int father)
{
	fa[u] = father;
	dep[u] = dep[father] + 1;
	siz[u] = 1;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == father)
		{
			continue;
		}
		dfs1(v, u);
		siz[u] += siz[v];
		if (siz[v] > siz[son[u]])
		{
			son[u] = v;
		}
	}
}

void dfs2(int u, int topp)
{
	dfn[u] = ++Time;
	top[u] = topp;
	if (son[u])
	{
		dfs2(son[u], topp);
	}
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (top[v])
		{
			continue;
		}
		dfs2(v, v);
	}
}

int n, m, r, p;
int a[MAXN], b[MAXN];

#define lson pos << 1
#define rson pos << 1 | 1

struct tree
{
	int l, r, siz, val, tag = 0;
}t[MAXN << 2];

void pushup(int pos)
{
	t[pos].val = (t[lson].val + t[rson].val) % p;
}

void pushdown(int pos)
{
	if (t[pos].tag)
	{
		t[lson].val = (t[lson].val + t[pos].tag * t[lson].siz) % p;
		t[rson].val = (t[rson].val + t[pos].tag * t[rson].siz) % p;
		t[lson].tag = (t[lson].tag + t[pos].tag) % p;
		t[rson].tag = (t[rson].tag + t[pos].tag) % p;
		t[pos].tag = 0;
	}
}

void build(int pos, int l, int r)
{
	t[pos].l = l, t[pos].r = r, t[pos].siz = r - l + 1;
	if (l == r)
	{
		t[pos].val = b[l] % p;
		return;
	}
	int mid = (l + r) >> 1;
	build(lson, l, mid);
	build(rson, mid + 1, r);
	pushup(pos);
}

void update(int pos, int L, int R, int k)
{
	int l = t[pos].l, r = t[pos].r;
	if (L <= l && r <= R)
	{
		t[pos].val = (t[pos].val + k * t[pos].siz) % p;
		t[pos].tag = (t[pos].tag + k) % p;
		return;
	}
	pushdown(pos);
	int mid = (l + r) >> 1;
	if (L <= mid)
	{
		update(lson, L, R, k);
	}
	if (R > mid)
	{
		update(rson, L, R, k);
	}
	pushup(pos);
}

int query(int pos, int L, int R)
{
	int l = t[pos].l, r = t[pos].r;
	if (L <= l && r <= R)
	{
		return t[pos].val;
	}
	pushdown(pos);
	int mid = (l + r) >> 1, res = 0;
	if (L <= mid)
	{
		res = query(lson, L, R);
	}
	if (R > mid)
	{
		res = (res + query(rson, L, R)) % p;
	}
	return res;
}

void update_path(int x, int y, int z)
{
	z %= p;
	while (top[x] != top[y])
	{
		if (dep[top[x]] < dep[top[y]])
		{
			swap(x, y);
		}
		update(1, dfn[top[x]], dfn[x], z);
		x = fa[top[x]];
	}
	if (dep[x] > dep[y])
	{
		swap(x, y);
	}
	update(1, dfn[x], dfn[y], z);
}

void update_subtree(int x, int z)
{
	z %= p;
	update(1, dfn[x], dfn[x] + siz[x] - 1, z);
}

int query_path(int x, int y)
{
	int res = 0;
	while (top[x] != top[y])
	{
		if (dep[top[x]] < dep[top[y]])
		{
			swap(x, y);
		}
		res = (res + query(1, dfn[top[x]], dfn[x])) % p;
		x = fa[top[x]];
	}
	if (dep[x] > dep[y])
	{
		swap(x, y);
	}
	res = (res + query(1, dfn[x], dfn[y])) % p;
	return res;
}

int query_subtree(int x)
{
	return query(1, dfn[x], dfn[x] + siz[x] - 1);
}

int main()
{
	scanf("%d%d%d%d", &n, &m, &r, &p);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", a + i);
	}
	for (int i = 1; i < n; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);
		add(y, x);
	}
	dfs1(r, 0);
	dfs2(r, r);
	for (int i = 1; i <= n; i++)
	{
		b[dfn[i]] = a[i];
	}
	build(1, 1, n);
	while (m--)
	{
		int op, x, y, z;
		scanf("%d%d", &op, &x);
		if (op == 1)
		{
			scanf("%d%d", &y, &z);
			update_path(x, y, z);
		}
		else if (op == 2)
		{
			scanf("%d", &y);
			printf("%d\n", query_path(x, y));
		}
		else if (op == 3)
		{
			scanf("%d", &z);
			update_subtree(x, z);
		}
		else
		{
			printf("%d\n", query_subtree(x));
		}
	}
	return 0;
}

6. ST 表

P3865 【模板】ST 表

tips:注意 lg 数组从 i = 2 开始预处理。

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e5 + 5;

int n, m;
int lg[MAXN], st[MAXN][18];

void init()
{
	for (int j = 1; (1 << j) <= n; j++)
	{
		for (int i = 1; i + (1 << j) - 1 <= n; i++)
		{
			st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
		}
	}
}

int query(int l, int r)
{
	int k = lg[r - l + 1];
	return max(st[l][k], st[r - (1 << k) + 1][k]);
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 2; i <= n; i++)
	{
		lg[i] = lg[i >> 1] + 1;
	}
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &st[i][0]);
	}
	init();
	while (m--)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		printf("%d\n", query(l, r));
	}
	return 0;
}

7. 单调类

(1) 单调队列

P1886 滑动窗口 /【模板】单调队列

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e6 + 5;

int a[MAXN], q[MAXN];

int main()
{
	int n, k;
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", a + i);
	}
	int head = 1, tail = 0;
	for (int i = 1; i <= n; i++)
	{
		while (head <= tail && i - q[head] >= k)
		{
			head++;
		}
		while (head <= tail && a[q[tail]] >= a[i])
		{
			tail--;
		}
		q[++tail] = i;
		if (i >= k)
		{
			printf("%d ", a[q[head]]);
		}
	}
	puts("");
	head = 1, tail = 0;
	for (int i = 1; i <= n; i++)
	{
		while (head <= tail && i - q[head] >= k)
		{
			head++;
		}
		while (head <= tail && a[q[tail]] <= a[i])
		{
			tail--;
		}
		q[++tail] = i;
		if (i >= k)
		{
			printf("%d ", a[q[head]]);
		}
	}
	return 0;
}

(2) 单调栈

P5788 【模板】单调栈

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 3e6 + 5;

int a[MAXN], st[MAXN], ans[MAXN];

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", a + i);
	}
	int top = 0;
	for (int i = n; i >= 1; i--)
	{
		while (top && a[st[top]] <= a[i])
		{
			top--;
		}
		ans[i] = st[top];
		st[++top] = i;
	}
	for (int i = 1; i <= n; i++)
	{
		printf("%d ", ans[i]);
	}
	return 0;
}

8. 平衡树

P6136 【模板】普通平衡树(数据加强版)

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;

const int MAXN = 1e6 + 5;

int tot, rt;

struct treap
{
	int lson, rson, val, key, siz;
}t[MAXN];

int build(int val)
{
	t[++tot] = treap{0, 0, val, rand(), 1};
	return tot;
}

void pushup(int pos)
{
	t[pos].siz = t[t[pos].lson].siz + t[t[pos].rson].siz + 1;
}

void split(int pos, int val, int &a, int &b)
{
	if (!pos)
	{
		a = b = 0;
		return;
	}
	if (t[pos].val <= val)
	{
		a = pos;
		split(t[pos].rson, val, t[a].rson, b);
	}
	else
	{
		b = pos;
		split(t[pos].lson, val, a, t[b].lson);
	}
	pushup(pos);
}

int merge(int a, int b)
{
	if (!a || !b)
	{
		return a + b;
	}
	if (t[a].key > t[b].key)
	{
		t[a].rson = merge(t[a].rson, b);
		pushup(a);
		return a;
	}
	else
	{
		t[b].lson = merge(a, t[b].lson);
		pushup(b);
		return b;
	}
}

void insert(int x)
{
	int a = 0, b = 0, c = build(x);
	split(rt, x, a, b);
	rt = merge(merge(a, c), b);
}

void remove(int x)
{
	int a = 0, b = 0, c = 0;
	split(rt, x - 1, a, b);
	split(b, x, b, c);
	b = merge(t[b].lson, t[b].rson);
	rt = merge(a, merge(b, c));
}

int getrnk(int x)
{
	int a = 0, b = 0;
	split(rt, x - 1, a, b);
	int res = t[a].siz + 1;
	rt = merge(a, b);
	return res;
}

int getval(int x)
{
	int pos = rt;
	while (pos)
	{
		if (t[t[pos].lson].siz + 1 == x)
		{
			break;
		}
		if (t[t[pos].lson].siz >= x)
		{
			pos = t[pos].lson;
		}
		else
		{
			x -= (t[t[pos].lson].siz + 1);
			pos = t[pos].rson;
		}
	}
	return t[pos].val;
}

int getpre(int x)
{
	int a = 0, b = 0;
	split(rt, x - 1, a, b);
	int pos = a;
	while (t[pos].rson)
	{
		pos = t[pos].rson;
	}
	rt = merge(a, b);
	return t[pos].val;
}

int getnxt(int x)
{
	int a = 0, b = 0;
	split(rt, x, a, b);
	int pos = b;
	while (t[pos].lson)
	{
		pos = t[pos].lson;
	}
	rt = merge(a, b);
	return t[pos].val;
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		int x;
		scanf("%d", &x);
		insert(x);
	}
	int lst = 0, ans = 0;
	while (m--)
	{
		int op, x;
		scanf("%d%d", &op, &x);
		x ^= lst;
		if (op == 1)
		{
			insert(x);
		}
		else if (op == 2)
		{
			remove(x);
		}
		else if (op == 3)
		{
			ans ^= (lst = getrnk(x));
		}
		else if (op == 4)
		{
			ans ^= (lst = getval(x));
		}
		else if (op == 5)
		{
			ans ^= (lst = getpre(x));
		}
		else
		{
			ans ^= (lst = getnxt(x));
		}
	}
	printf("%d\n", ans);
	return 0;
}

9. 分块

P3372 【模板】线段树 1

#include <iostream>
#include <cstdio>
#include <cmath>
#define int long long
using namespace std;

const int MAXN = 1e5 + 5;

int a[MAXN], block[MAXN], L[MAXN], R[MAXN], sum[MAXN], add[MAXN];

void update(int l, int r, int k)
{
	int p = block[l], q = block[r];
	if (p == q)
	{
		for (int i = l; i <= r; i++)
		{
			a[i] += k;
		}
		sum[p] += k * (r - l + 1);
	}
	else
	{
		for (int i = l; i <= R[p]; i++)
		{
			a[i] += k;
		}
		sum[p] += k * (R[p] - l + 1);
		for (int i = p + 1; i < q; i++)
		{
			add[i] += k;
		}
		for (int i = L[q]; i <= r; i++)
		{
			a[i] += k;
		}
		sum[q] += k * (r - L[q] + 1);
	}
}

int query(int l, int r)
{
	int p = block[l], q = block[r], res = 0;
	if (p == q)
	{
		for (int i = l; i <= r; i++)
		{
			res += a[i];
		}
		res += add[p] * (r - l + 1);
	}
	else
	{
		for (int i = l; i <= R[p]; i++)
		{
			res += a[i];
		}
		res += add[p] * (R[p] - l + 1);
		for (int i = p + 1; i < q; i++)
		{
			res += sum[i] + add[i] * (R[i] - L[i] + 1);
		}
		for (int i = L[q]; i <= r; i++)
		{
			res += a[i];
		}
		res += add[q] * (r - L[q] + 1);
	}
	return res;
}

signed main()
{
	int n, m;
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", a + i);
	}
	int k = sqrt(n);
	for (int i = 1; i <= k; i++)
	{
		L[i] = R[i - 1] + 1;
		R[i] = i * k;
	}
	if (R[k] < n)
	{
		k++;
		L[k] = R[k - 1] + 1;
		R[k] = n;
	}
	for (int i = 1; i <= k; i++)
	{
		for (int j = L[i]; j <= R[i]; j++)
		{
			block[j] = i;
			sum[i] += a[j];
		}
	}
	while (m--)
	{
		int op, x, y, k;
		scanf("%lld%lld%lld", &op, &x, &y);
		if (op == 1)
		{
			scanf("%lld", &k);
			update(x, y, k);
		}
		else
		{
			printf("%lld\n", query(x, y));
		}
	}
	return 0;
}

四、数学

1. 质数

P3383 【模板】线性筛素数

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e8 + 5;

int p[MAXN];
bool vis[MAXN];

void pre(int n)
{
	for (int i = 2; i <= n; i++)
	{
		if (!vis[i])
		{
			p[++p[0]] = i;
		}
		for (int j = 1; j <= p[0] && i * p[j] <= n; j++)
		{
			if (i % p[j] == 0)
			{
				break;
			}
			vis[i * p[j]] = true;
		}
	}
}

int main()
{
	int n, q;
	scanf("%d%d", &n, &q);
	pre(n);
	while (q--)
	{
		int k;
		scanf("%d", &k);
		printf("%d\n", p[k]);
	}
	return 0;
}

2. 快速幂

P1226 【模板】快速幂||取余运算

tips:不能直接 printf("%lld^%lld mod %lld=%lld\n", a, b, p, ksm());,因为是从后往前处理,输出 \(b\) 时快速幂已经结束,\(b=0\)

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

int a, b, p;

int ksm()
{
	int base = a, ans = 1ll;
	while (b)
	{
		if (b & 1ll)
		{
			ans = ans * base % p;
		}
		base = base * base % p;
		b >>= 1ll;
	}
	return ans;
}

signed main()
{
	scanf("%lld%lld%lld", &a, &b, &p);
	printf("%lld^%lld mod %lld=", a, b, p);
	printf("%lld\n", ksm());
	return 0;
}

3. 矩阵

(1) 矩阵快速幂

P3390 【模板】矩阵快速幂

#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;

const int MAXN = 105;
const int MOD = 1e9 + 7;

int n, k;

struct matrix
{
	int a[MAXN][MAXN];
	void init()
	{
		memset(a, 0, sizeof(a));
	}
	void build()
	{
		for (int i = 1; i <= n; i++)
		{
			a[i][i] = 1;
		}
	}
	matrix operator *(const matrix &x)const
	{
		matrix res;
		res.init();
		for (int k = 1; k <= n; k++)
		{
			for (int i = 1; i <= n; i++)
			{
				for (int j = 1; j <= n; j++)
				{
					res.a[i][j] = (res.a[i][j] + x.a[i][k] * a[k][j]) % MOD;
				}
			}
		}
		return res;
	}
};

matrix ksm(matrix a, int b)
{
	matrix base = a, ans;
	ans.init(), ans.build();
	while (b)
	{
		if (b & 1)
		{
			ans = ans * base;
		}
		base = base * base;
		b >>= 1;
	}
	return ans;
}

signed main()
{
	scanf("%lld%lld", &n, &k);
	matrix a;
	a.init();
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			scanf("%lld", &a.a[i][j]);
		}
	}
	matrix ans = ksm(a, k);
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			printf("%lld ", ans.a[i][j]);
		}
		puts("");
	}
	return 0;
}

(2) 矩阵加速数列

P1939 【模板】矩阵加速(数列)

#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;

const int MOD = 1e9 + 7;

struct matrix
{
	int a[4][4];
	void init()
	{
		memset(a, 0, sizeof(a));
	}
	void build()
	{
		for (int i = 1; i <= 3; i++)
		{
			a[1][i] = 1;
		}
	}
	matrix operator *(const matrix &x)const
	{
		matrix res;
		res.init();
		for (int k = 1; k <= 3; k++)
		{
			for (int i = 1; i <= 3; i++)
			{
				for (int j = 1; j <= 3; j++)
				{
					res.a[i][j] = (res.a[i][j] + a[i][k] * x.a[k][j]) % MOD;
				}
			}
		}
		return res;
	}
};

matrix ksm(int b)
{
	matrix base, ans;
	base.init();
	base.a[1][1] = base.a[1][2] = base.a[2][3] = base.a[3][1] = 1;
	ans.init(), ans.build();
	while (b)
	{
		if (b & 1)
		{
			ans = ans * base;
		}
		base = base * base;
		b >>= 1;
	}
	return ans;
}

signed main()
{
	int t;
	scanf("%lld", &t);
	while (t--)
	{
		int n;
		scanf("%lld", &n);
		if (n <= 3)
		{
			puts("1");
		}
		else
		{
			printf("%lld\n", ksm(n - 3).a[1][1]);
		}
	}
	return 0;
}

4. 博弈论

P2197 【模板】nim 游戏

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e4 + 5;

int t, n;
int a[MAXN];

int xor_sum()
{
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		ans ^= a[i];
	}
	return ans;
}

int main()
{
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d", &n);
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", a + i);
		}
		if (!xor_sum())
		{
			puts("No");
		}
		else
		{
			puts("Yes");
		}
	}
	return 0;
}

5. exgcd

P1082 [NOIP2012 提高组] 同余方程

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

int x, y;

void exgcd(int a, int b)
{
	if (!b)
	{
		x = 1, y = 0;
		return;
	}
	exgcd(b, a % b);
	int tmp = x;
	x = y;
	y = tmp - a / b * y;
}

signed main()
{
	int a, b;
	scanf("%lld%lld", &a, &b);
	exgcd(a, b);
	printf("%lld\n", (x % b + b) % b);
	return 0;
}

6. 逆元

(1) 单个求

P2613 【模板】有理数取余

#include <iostream>
#include <cstdio>
#define int long long
#define re register
using namespace std;

const int MOD = 19260817;

inline int read()
{
    re int f = 1, x = 0;
    re char c = getchar();
	while (c < '0' || c > '9')
	{
	    if (c == '-')
		{
			f = -1;
		}
	    c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		x = ((x << 3) + (x << 1) + c - '0') % MOD;
		c = getchar();
	}
	return f * x;
}

int ksm(int a, int b)
{
	int base = a, ans = 1;
	while (b)
	{
		if (b & 1)
		{
			ans = ans * base % MOD;
		}
		base = base * base % MOD;
		b >>= 1;
	}
	return ans;
}

signed main()
{
	int a = read(), b = read();
	if (b)
	{
		printf("%lld\n", a * ksm(b, MOD - 2) % MOD);
	}
	else
	{
		puts("Angry!");
	}
	return 0;
}

(2) 线性推

P3811 【模板】乘法逆元

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

const int MAXN = 3e6 + 5;

int inv[MAXN];

signed main()
{
	int n, p;
	scanf("%lld%lld", &n, &p);
	inv[1] = 1;
	puts("1");
	for (int i = 2; i <= n; i++)
	{
		inv[i] = (-(p / i) * inv[p % i] % p + p) % p;
		printf("%lld\n", inv[i]);
	}
	return 0;
}

7. 扩展欧拉定理

P5091 【模板】扩展欧拉定理

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

int a, m, b, phi;

void pre()
{
	phi = m;
	int tm = m;
	for (int i = 2; i * i <= tm; i++)
	{
		if (tm % i == 0)
		{
			phi = phi / i * (i - 1);
			while (tm % i == 0)
			{
				tm /= i;
			}
		}
	}
	if (tm > 1)
	{
		phi = phi / tm * (tm - 1);
	}
}

bool flag;

inline int read()
{
	int x = 0, f = 1;
	char c = getchar();
	while (c < '0' || c > '9')
	{
		if (c == '-')
		{
			f = -1;
		}
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		x = (x << 3) + (x << 1) + c - '0';
		if (x >= phi)
		{
			flag = true;
		}
		x %= phi;
		c = getchar();
	}
	return x * f;
}

int ksm(int a, int b)
{
	int base = a, ans = 1;
	while (b)
	{
		if (b & 1)
		{
			ans = ans * base % m;
		}
		base = base * base % m;
		b >>= 1;
	}
	return ans;
}

signed main()
{
	scanf("%lld%lld", &a, &m);
	pre();
	b = read();
	if (flag)
	{
		b += phi;
	}
	printf("%lld\n", ksm(a, b));
	return 0;
}

8. 裴蜀定理

P4549 【模板】裴蜀定理

#include <iostream>
#include <cstdio>
using namespace std;

int gcd(int a, int b)
{
	if (!b)
	{
		return a;
	}
	return gcd(b, a % b);
}

int main()
{
	int n, ans = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		int a;
		scanf("%d", &a);
		a = abs(a);
		ans = gcd(ans, a);
	}
	printf("%d\n", ans);
	return 0;
}

五、字符串

1. KMP

P3375 【模板】KMP字符串匹配

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 1e6 + 5;

char s1[MAXN], s2[MAXN];
int nxt[MAXN];

int main()
{
	scanf("%s%s", s1 + 1, s2 + 1);
	int n = strlen(s1 + 1), m = strlen(s2 + 1);
	for (int i = 2, j = 0; i <= m; i++)
	{
		while (j && s2[i] != s2[j + 1])
		{
			j = nxt[j];
		}
		if (s2[i] == s2[j + 1])
		{
			j++;
		}
		nxt[i] = j;
	}
	for (int i = 1, j = 0; i <= n; i++)
	{
		while (j && s1[i] != s2[j + 1])
		{
			j = nxt[j];
		}
		if (s1[i] == s2[j + 1])
		{
			j++;
		}
		if (j == m)
		{
			printf("%d\n", i - m + 1);
		}
	}
	for (int i = 1; i <= m; i++)
	{
		printf("%d ", nxt[i]);
	}
	return 0;
}

2. manacher

P3805 【模板】manacher 算法

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 1.1e7 + 5;

int n, l;
char a[MAXN], s[MAXN << 1];

void init()
{
	n = strlen(a + 1);
	s[++l] = '$';
	s[++l] = '#';
	for (int i = 1; i <= n; i++)
	{
		s[++l] = a[i];
		s[++l] = '#';
	}
}

int len[MAXN << 1];

void manacher()
{
	int maxr = 0, mid = 0;
	for (int i = 1; i <= l; i++)
	{
		if (i <= maxr)
		{
			len[i] = min(maxr - i + 1, len[(mid << 1) - i]);
		}
		while (s[i - len[i]] == s[i + len[i]])
		{
			len[i]++;
		}
		if (i + len[i] - 1 > maxr)
		{
			maxr = i + len[i] - 1;
			mid = i;
		}
	}
}

int main()
{
	scanf("%s", a + 1);
	init();
	manacher();
	int ans = 0;
	for (int i = 1; i <= l; i++)
	{
		ans = max(ans, len[i] - 1);
	}
	printf("%d\n", ans);
	return 0;
}

3. trie 树

P2580 于是他错误的点名开始了

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 1e4 + 5;

int tot;
int trie[MAXN * 55][26];

void insert(char *s)
{
	int n = strlen(s), pos = 0;
	for (int i = 0; i < n; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot;
		}
		pos = trie[pos][c];
	}
}

bool vis[MAXN * 55];

int search(char *s)
{
	int n = strlen(s), pos = 0;
	for (int i = 0; i < n; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			return 0;
		}
		pos = trie[pos][c];
	}
	if (!vis[pos])
	{
		vis[pos] = true;
		return 1;
	}
	else
	{
		return 2;
	}
}

char s[55];

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%s", s);
		insert(s);
	}
	int m;
	scanf("%d", &m);
	while (m--)
	{
		scanf("%s", s);
		int res = search(s);
		if (!res)
		{
			puts("WRONG");
		}
		else if (res == 1)
		{
			puts("OK");
		}
		else
		{
			puts("REPEAT");
		}
	}
	return 0;
}

4. AC 自动机

(1) 模板

P3808 【模板】AC自动机(简单版)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int MAXN = 1e6 + 5;

int tot;
int trie[MAXN][26], ed[MAXN], fail[MAXN];
bool vis[MAXN];

void insert(char *s)
{
	int n = strlen(s), pos = 0;
	for (int i = 0; i < n; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot;
		}
		pos = trie[pos][c];
	}
	ed[pos]++;
}

int search(char *s)
{
	int n = strlen(s), pos = 0, ans = 0;
	for (int i = 0; i < n; i++)
	{
		int c = s[i] - 'a';
		pos = trie[pos][c];
		for (int j = pos; j && !vis[j]; j = fail[j])
		{
			ans += ed[j];
			vis[j] = true;
		}
	}
	return ans;
}

queue<int> q;

void build()
{
	for (int c = 0; c < 26; c++)
	{
		if (trie[0][c])
		{
			q.push(trie[0][c]);
		}
	}
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		for (int c = 0; c < 26; c++)
		{
			int v = trie[u][c];
			if (v)
			{
				fail[v] = trie[fail[u]][c];
				q.push(v);
			}
			else
			{
				trie[u][c] = trie[fail[u]][c];
			}
		}
	}
}

char s[MAXN], t[MAXN];

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%s", s);
		insert(s);
	}
	build();
	scanf("%s", t);
	printf("%d\n", search(t));
	return 0;
}

(2) 拓扑优化

P5357 【模板】AC自动机(二次加强版)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int MAXN = 2e5 + 5;

int cnt;
int head[MAXN], siz[MAXN];

struct edge
{
	int to, nxt;
}e[MAXN];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

void dfs(int u)
{
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		dfs(v);
		siz[u] += siz[v];
	}
}

int tot;
int trie[MAXN][26], id[MAXN], fail[MAXN];

void insert(char *s, int num)
{
	int n = strlen(s), pos = 0;
	for (int i = 0; i < n; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot;
		}
		pos = trie[pos][c];
	}
	id[num] = pos;
}

void search(char *s)
{
	int n = strlen(s), pos = 0;
	for (int i = 0; i < n; i++)
	{
		int c = s[i] - 'a';
		pos = trie[pos][c];
		siz[pos]++;
	}
	for (int i = 1; i <= tot; i++)
	{
		add(fail[i], i);
	}
}

queue<int> q;

void build()
{
	for (int c = 0; c < 26; c++)
	{
		if (trie[0][c])
		{
			q.push(trie[0][c]);
		}
	}
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		for (int c = 0; c < 26; c++)
		{
			int v = trie[u][c];
			if (v)
			{
				fail[v] = trie[fail[u]][c];
				q.push(v);
			}
			else
			{
				trie[u][c] = trie[fail[u]][c];
			}
		}
	}
}

char t[2000005], s[2000005];

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%s", t);
		insert(t, i);
	}
	build();
	scanf("%s", s);
	search(s);
	dfs(0);
	for (int i = 1; i <= n; i++)
	{
		printf("%d\n", siz[id[i]]);
	}
	return 0;
}
posted @ 2021-10-11 13:50  mango09  阅读(104)  评论(0编辑  收藏  举报
-->