CSP 普及 & 提高 考点 模板合集

零、杂项

加速 cin/coutios::sync_with_stdio(false);。注:放在 main 函数的第一行,但使用它之后不能使用 scanf/printf

避坑/防爆0 指南。

快读:

inline int rd(){
    int x = 1, s = 0; char ch = getchar();
    while(ch < '0' or ch > '9'){if(ch == '-') x = -1; ch = getchar();}
    while(ch >= '0' and ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return x * s;
}

快写:

inline void wr(int x){
    if(x < 0) putchar('-'), x *= -1;
    if(x > 9) wr(x / 10);
    putchar(x % 10 + '0');
}

一、博弈论

Bash/Wythoff/Nimm Game

二、字符串

KMP

洛谷P3375 【模板】KMP字符串匹配

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1000010;
int kmp[maxn];
int lena, lenb;
char a[maxn], b[maxn];

int main ()
{
	scanf ("%s %s", a + 1, b + 1);
	lena = strlen (a + 1), lenb = strlen (b + 1);
	for (int i = 2, j = 0; i <= lenb; i++)
	{
		while (j and b[i] != b[j + 1]) j = kmp[j];
		if (b[i] == b[j + 1]) j++;
		kmp[i] = j;
	}
	int j = 0;
	for (int i = 1; i <= lena; i++)
	{
		while (j and b[j + 1] != a[i]) j = kmp[j];
		if (b[j + 1] == a[i]) j++;
		if (j == lenb)
		{
			printf ("%d\n", i - lenb + 1);
			j = kmp[j];
		}
	}
	for (int i = 1; i <= lenb; i++) printf ("%d ", kmp[i]);
	return 0;
}

AC自动机

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

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
//#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e6 + 5;
int n, tot;
int trie[maxn][30], ans[maxn], fail[maxn];
string str, s;

inline void add ()
{
	int nw = 0, len = str.size ();
	for (int i = 0; i < len; i++)
	{
		int nxt = str[i] - 'a' + 1;
		if (!trie[nw][nxt]) trie[nw][nxt] = ++tot;
		nw = trie[nw][nxt];
	}
	ans[nw]++;
}

queue <int> q;

inline void getfail ()
{
	for (int i = 1; i <= 26; i++) 
	{
		if (!trie[0][i]) continue;
		fail[trie[0][i]] = 0;
		q.push (trie[0][i]);
	}
	while (!q.empty ())
	{
		int nw = q.front ();
		q.pop ();
		for (int i = 1; i <= 26; i++)
		{
			if (trie[nw][i]) 
			{
				fail[trie[nw][i]] = trie[fail[nw]][i];
				q.push (trie[nw][i]);
			}
			else trie[nw][i] = trie[fail[nw]][i];
		}
	}
}

inline int find ()
{
	int len = s.size (), res = 0, nw = 0;
	for (int i = 0; i < len; i++)
	{
		nw = trie[nw][s[i] - 'a' + 1];
		for (int j = nw; j and ans[j] != -1; j = fail[j]) 
		{
			res += ans[j];
			ans[j] = -1;
		}
	}
	return res;
}

int main ()
{
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		cin >> str;
		add ();
	}
	getfail ();
	cin >> s;
	printf ("%d\n", find ());
	return 0;
}

字符串哈希

单哈希:洛谷P3370 【模板】字符串哈希

code by 皎月半洒花。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef unsigned long long ull;
ull base=131;
ull a[10010];
char s[10010];
int n,ans=1;
int prime=233317; 
ull mod=212370440130137957ll;
ull hashe(char s[])
{
 int len=strlen(s);
 ull ans=0;
 for (int i=0;i<len;i++)
 ans=(ans*base+(ull)s[i])%mod+prime;
 return ans;
}
int main()
{
 scanf("%d",&n);
 for(int i=1;i<=n;i++)
 {
 	scanf("%s",s);
 	a[i]=hashe(s);
 }
 sort(a+1,a+n+1);
 for(int i=1;i<n;i++)
 {
 	if(a[i]!=a[i+1])
 	ans++;
 }
 printf("%d",ans);
} 

双哈希的应用:洛谷AT_arc099_d [ARC099D] Eating Symbols Hard

#include<bits/stdc++.h>
using namespace std;

#define rep(i, a, b) for(register int i = a; i <= b; ++i)
typedef long long ll;
const int maxn = 250005;
const int mod1 = 1e9 + 7, mod2 = 998244353;
int n, p[maxn], bs = 121;
char c[maxn];
map<pair<int, int>, int> mp;
pair<int, int> hsh[maxn];
ll ans;

inline int pw(int x, int p, int mod){
	int res = 1;
	while(p){
		if(p & 1) 
			res = 1ll * res * x % mod;
		p /= 2, x = 1ll * x * x % mod;
	} return res;
}
inline pair<int, int> gh(int x, int y){
	int a, b; a = b = x;
	if(y < 0) 
		a = pw(a, mod1 - 2, mod1), b = pw(b, mod2 - 2, mod2), y *= -1; 
	return make_pair(pw(a, y, mod1), pw(b, y, mod2));
}

inline pair<int, int> add(pair<int, int> a, pair<int, int> b){
	return make_pair((a.first + b.first) % mod1, (a.second + b.second) % mod2);
}
inline pair<int, int> mns(pair<int, int> a, pair<int, int> b){
	return make_pair((a.first + mod1 - b.first) % mod1, (a.second + mod2 - b.second) % mod2);
}
inline pair<int, int> tim(pair<int, int> a, pair<int, int> b){
	return make_pair((1ll * a.first * b.first) % mod1, (1ll * a.second * b.second) % mod2);
}

int main(){
	scanf("%d", &n); 
	rep(i, 1, n){ 
		char c; cin >> c; 
		p[i] = p[i - 1];
		if(c == '+') hsh[i] = add(hsh[i - 1], gh(bs, p[i]));
		if(c == '-') hsh[i] = mns(hsh[i - 1], gh(bs, p[i]));
		if(c == '<') hsh[i] = hsh[i - 1], p[i] -= 1;
		if(c == '>') hsh[i] = hsh[i - 1], p[i] += 1;
		mp[hsh[i]] += 1;
	}
	rep(i, 1, n)
		ans += mp[add(hsh[i - 1], tim(hsh[n], gh(bs, p[i - 1])))], 
		mp[hsh[i]] -= 1;
	printf("%lld\n", ans);
	return 0;
}

Trie树(图)

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

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

const int maxn = 800005;
int bl[maxn], ch[maxn][30], tot = 1, vis[maxn];
int n, m;
char nam[55];

inline int read ()
{
	int x = 1, s = 0;
	char ch = getchar ();
	while (ch < '0' or ch > '9'){if (ch == '-') x = -1; ch = getchar ();}
	while (ch >= '0' and ch <= '9') s = s * 10 + ch - '0', ch = getchar ();
	return x * s;
}

inline void insrt ()
{
	int p, u = 0, len = strlen (nam);
	for (int i = 0; i <= len; i++)
	{
		if (i == len) 
		{
			bl[u]++;
			break;
		}
		p = nam[i] - 'a' + 1;
		if (ch[u][p])
		{
			u = ch[u][p];
			continue;
		}
		ch[u][p] = ++tot;
		u = ch[u][p];
	}
}

inline void dl ()
{
	int u = 0, p, len = strlen (nam);
	for (int i = 0; i < len; i++) 
	{
		p = nam[i] - 'a' + 1;
		if (!ch[u][p]) 
		{
			printf ("WRONG\n");
			return;
		}
		u = ch[u][p];
	}
	if (!bl[u])
	{
		printf ("WRONG\n");
		return;
	}
	if (!vis[u]) 
	{
		printf ("OK\n");
		vis[u]++;
		return;
	}
	printf ("REPEAT\n");
	return;
}

int main ()
{
	n = read ();
	for (int i = 1; i <= n; i++) 
	{
		scanf ("%s", nam);
		insrt ();
	}
	m = read ();
	for (int i = 1; i <= m; i++)
	{
		scanf ("%s", nam);
		dl ();
	}
	return 0;
}

manacher

洛谷P3805 【模板】manacher 算法

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

//#define int long long
const int maxn = 11000005;
char st[maxn * 2];
int len[maxn * 2], cnt, ans;
int mx, po;

inline void input ()
{
	char ch = getchar ();
	st[0] = '~', st[cnt = 1] = '#';
	while (ch < 'a' or ch > 'z') ch = getchar ();
	while (ch >= 'a' and ch <= 'z') st[++cnt] = ch, st[++cnt] = '#', ch = getchar (); 
}

signed main ()
{
	input ();
	for (int i = 1; i <= cnt; ++i)
	{
		if (i <= mx) len[i] = min (mx - i + 1, len[2 * po - i]);
		else len[i] = 1;
		while (st[i - len[i]] == st[i + len[i]]) len[i]++;
		if (len[i] + i > mx) mx = len[i] + i - 1, po = i;
		ans = max (ans, len[i]);
	}
	printf ("%d\n", ans - 1);
	return 0;
}

【To Do】最小表示法

最短路

spfa (判负环)

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

考场慎用,最慢 O(nm)

判负环:记录被松弛次数,如果某一节点的次数 n,证明存在负环。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
//const long long inf = 2147483647;
const int N = 1e5 + 10, M = 5e5 + 10;
int n, st, ed, dis[N];
int wt[M], hd[M], nxt[M], edg[M], idx;
bool vis[N];

void spfa()
{
    memset(dis, 0x3f, sizeof dis);
    dis[st] = 0;
    queue <int> q;
    q.push(st);
    //vis[st] = true;
    while (!q.empty())
    {
        int tt = q.front();
        q.pop();
        vis[tt] = false;
        for (int i = hd[tt]; i; i = nxt[i])
        {
            if (dis[tt] + wt[i] < dis[edg[i]])
            {
                dis[edg[i]] = dis[tt] + wt[i];
                if (!vis[edg[i]])
                {
                    q.push(edg[i]);
                    //vis[edg[i]] = true;
                }
            }
		}
    }
}
void add(int a, int b, int c)
{
    wt[++idx] = c;
    edg[idx] = b;
    nxt[idx] = hd[a];
    hd[a] = idx;
}
int main()
{
    //memset(nxt, -1, sizeof nxt);
    int a, b, c, t;
    cin >> n >> t >> st;
    for (int i = 1; i <= t; i++)
    {
        cin >> a >> b >> c;
        add(a, b, c);
    }
    spfa();
    for (int i = 1; i <= n; i++)
        if (st == i)cout << 0 << " ";
        else if (dis[i] == dis[0])cout << 2147483647 << " ";
        else cout << dis[i] << " ";
    return 0;
}

dijkstra (堆优化 & 线段树优化)

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

Code by little_sun.

#include<bits/stdc++.h>

const int MaxN = 100010, MaxM = 500010;

struct edge
{
    int to, dis, next;
};

edge e[MaxM];
int head[MaxN], dis[MaxN], cnt;
bool vis[MaxN];
int n, m, s;

inline void add_edge( int u, int v, int d )
{
    cnt++;
    e[cnt].dis = d;
    e[cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

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

std::priority_queue<node> q;


inline void dijkstra()
{
    dis[s] = 0;
    q.push( ( node ){0, s} );
    while( !q.empty() )
    {
        node tmp = q.top();
        q.pop();
        int x = tmp.pos, d = tmp.dis;
        if( vis[x] )
            continue;
        vis[x] = 1;
        for( int i = head[x]; i; i = e[i].next )
        {
            int y = e[i].to;
            if( dis[y] > dis[x] + e[i].dis )
            {
                dis[y] = dis[x] + e[i].dis;
                if( !vis[y] )
                {
                    q.push( ( node ){dis[y], y} );
                }
            }
        }
    }
}


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

倍增 floyd

洛谷P3371 【模板】单源最短路径(弱化版) 70 pts.

code by Nemlit.

#include<bits/stdc++.h>
using namespace std;
#define inf 1234567890
#define maxn 10005
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;
}//快读
int a[maxn][maxn],n,m,s;
inline void floyd()
{
    for(int k=1;k<=n;k++)
    //这里要先枚举k(可以理解为中转点)
	{
        for(int i=1;i<=n;i++)
		{
            if(i==k||a[i][k]==inf)
            {
                continue;
			}
			for(int j=1;j<=n;j++)
			{
                a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
                //松弛操作,即更新每两个点之间的距离
                //松弛操作有三角形的三边关系推出
                //即两边之和大于第三边
            }
        }
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=1;i<=n;i++)
	{
        for(int j=1;j<=n;j++)
		{
            a[i][j]=inf;
        }
    }
	//初始化,相当于memset(a,inf,sizeof(a))
    for(int i=1,u,v,w;i<=m;i++)
	{
        u=read(),v=read(),w=read();
        a[u][v]=min(a[u][v],w);
        //取min可以对付重边
    }
    floyd();
    a[s][s]=0;
    for(int i=1;i<=n;i++)
    {
        printf("%d ",a[s][i]);
    }
    return 0;
}

差分约束

P5960 【模板】差分约束算法

#include<bits/stdc++.h>
using namespace std;

int n, m;
int flag = 0;
int ans = -0x7f;
int hd[10005];
int cnt;
int vis[10005];
int dis[10005];
int inque[10005];

struct node{
	int to;
	int w, nxt;
}e[10005];

void add(int u, int v, int w)
{
	e[++cnt].w = w;
	e[cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void spfa ()
{
	memset (dis, 0x3f3f3f3f, sizeof (dis));
	memset (vis, 0, sizeof (vis));
	queue<int>q;
	dis[0] = 0;
	vis[0] = 1;
	inque[0] = 1;
	q.push(0);
	while (!q.empty())
	{
		int x;
		x = q.front();
		vis[x] = 0;
		q.pop();
		for (int i = hd[x]; i; i = e[i].nxt)
		{
			int v = e[i].to, w = e[i].w;
			if (dis[v] > dis[x] + w)
			{
				dis[v] = dis[x] + w;
				
				if (vis[v] == 0)
				{
					q.push(v);
					vis[v] = 1;
					++inque[v];
					if (inque[v] > n)
					{
						cout << "NO" << endl;
						flag = 1;
						return;
					}
				}
				
			}
		}
	}
	//int minn;
	//for (int i = 1; i <= n; i++)minn = min (minn, dis[i]);
	for (int i = 1; i <= n; ++i)cout << dis[i] << " ";
//	cout << "NO" << endl;
	return;
}

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int a, b, c;
		cin >> a >> b >> c;
		add (b, a, c);
	}
	for (int i = 1; i <= n; i++)add (0, i, 0);
	spfa();
	return 0;
}

Tarjan

强连通分量/缩点

强连通分量。

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 100005;
int n, m;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[maxn * 2]; 
int dfn[maxn], low[maxn];
int tmp, top;
int st[maxn];
int co[maxn], col;

void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;//step 1
	st[++top] = u;//step 2
	for (int i = hd[u]; i; i = e[i].nxt)//step 3
	{
		int v = e[i].to;
		if (!dfn[v])//如果没有被访问过
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (!co[v]) //如果它还不在一个强连通分量内
        low[u] = min (low[u], dfn[v]);
		
	}
	if (low[u] == dfn[u])//step 4
	{
		co[u] = ++col;
		while (st[top] != u)//弹栈操作
		{
			co[st[top]] = col;
			--top;
		} 
		--top;//最后记得把 u 也弹出去
	}
}

int vis[maxn];

signed main ()
{
	scanf ("%lld %lld", &n, &m);
	for	(int i = 1; i <= m; i++)
	{
		int u, v;
		scanf ("%lld %lld", &u, &v);
		add (u, v);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i]) tarjan (i);
	}
	printf ("%lld\n", col);
	for (int i = 1; i <= n; i++)
	{
		if (vis[i]) continue;
		printf ("%lld ", i);
		vis[i] = 1;
		for (int j = i + 1; j <= n; j++)
		{
			if (co[j] == co[i])
			{
				printf ("%lld ", j);
				vis[j] = 1;
			}
		}
		printf ("\n");
	}
	return 0;
}

缩点:P3387 【模板】缩点.

#include<bits/stdc++.h>
using namespace std;

const int maxn = 150005;
int n, m;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[maxn * 2];
int dfn[maxn], low[maxn];
int top, st[maxn], de[maxn], si[maxn];
int col, co[maxn];
int x[maxn], y[maxn];
int tmp, ans;
int f[maxn], sum[maxn];
int w[maxn];

void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[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 (!co[v]) low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])
	{
		co[u] = ++col;
		sum[col] += w[u];
		while (st[top] != u)
		{
			co[st[top]] = col;
			sum[col] += w[st[top]];
			--top;
		}
		--top;
	}
}

void search (int u)
{
	if (f[u]) return;
	f[u] = sum[u];
	int maxsum = 0;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!f[v]) search (v);
		maxsum = max (maxsum, f[v]);
	}
	f[u] += maxsum;
}

int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf ("%d", &w[i]);
	for (int i = 1; i <= m; i++)
	{
		scanf ("%d %d", &x[i], &y[i]);
		add (x[i], y[i]);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i]) tarjan (i);
	}
	cnt = 0;
	memset (hd, 0, sizeof hd);
	memset (e, 0, sizeof e);
	for (int i = 1; i <= m; i++)
	{
		if (co[x[i]] != co[y[i]])
		{
			add (co[x[i]], co[y[i]]);
		}
	}
	for (int i = 1; i <= col; i++)
	{
		if (!f[i]) 
		{
			search (i);
			ans = max (ans, f[i]);
		}
	}
	printf ("%d\n", ans);
	return 0;
}

点双边双

《割点割边 + 点双边双 学习笔记》

点双:

void tarjan (int u, int f)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	co[u] = 1;
	if (u == rt and !hd[u])
	{
		cout << u << endl;
		return;
	}
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan (v, u);
			low[u] = min (low[u], low[v]);
			if (dfn[u] <= low[v])
			{
				col++;
				while (st[top] != u)
				{
					cout << st[top] << " ";
					co[st[top]] = 0;
					if (st[top] == v) {top--;break;}
					top--;
				}
				cout << u << endl;
			}
		}
		else if (v != f) low[u] = min (low[u], dfn[v]);
	}
}

边双:

inline void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[u]; i; i = e[i].nxt)
	if (!vis[i])
	{
		int v = e[i].to;
		vis[i] = vis[i ^ 1] = 1;
		if (!dfn[v])
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])//是环就直接倒出来
	{
		co[u] = ++col;
		while (st[top] != u)
		{
			co[st[top]] = col;
			top--;
		}
		top--;
	}
}

割点割边、桥

《割点割边 + 点双边双 学习笔记》

割点:

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	int tot = 0;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tot++;
			tarjan (v);
			low[u] = min (low[u], low[v]);
			if ((u == root and tot > 1) or (u != root and dfn[u] <= low[v])) vis[u] = 1;
		}
		else low[u] = min (low[u], dfn[v]);
	}
}

割边(桥):

void tarjan (int u, int li)
{
	dfn[u] = low[u] = ++tmp;
	for (int i = hd[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]) ans[++cntn] = (abc){min (u, v), max (u, v)};
		}
		else if (i != find (li)) low[u] = min (low[u], dfn[v]);
	}
}

2-sat

拓扑排序

洛谷P1137 旅行计划

#include<bits/stdc++.h>
using namespace std;

#define rint register int
const int maxn = 2e5 + 5;
int n, m;
int dis[maxn], r[maxn];
vector <int> e[maxn];

inline int read ()
{
	int s = 0, x = 1;
	char ch = getchar ();
	while (ch < '0' or ch > '9')
	{
		if (ch == '-') x = -1;
		ch = getchar ();
	}
	while (ch >= '0' and ch <= '9') 
		s = s * 10 + ch - '0', ch = getchar ();
	return x * s;
}

inline void topo ()
{
	queue <int> q;
	for (rint i (1); i <= n; ++i)
		if (!r[i])
			q.push (i), dis[i] = 1;
	while (!q.empty ())
	{
		int u = q.front ();
		q.pop ();
		for (rint i (0); i < e[u].size (); ++i)
		{
			int v = e[u][i];
			r[v] -= 1;
			if (!r[v]) 
			{
				q.push (v);
				dis[v] = dis[u] + 1;
			}
		}
	}
}

int main ()
{
	n = read (), m = read ();
	for (rint i (1); i <= m; ++i)
	{
		int u, v;
		u = read (), v = read ();
		e[u].push_back (v);
		r[v] += 1;
	}
	topo ();
	for (rint i (1); i <= n; ++i) 
		printf ("%d\n", dis[i]);
	return 0;
}

割边:

void tarjan (int u, int li)
{
	dfn[u] = low[u] = ++tmp;
	for (int i = hd[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]) ans[++cntn] = (abc){min (u, v), max (u, v)};
		}
		else if (i != find (li)) low[u] = min (low[u], dfn[v]);
	}
}

二分图(最大匹配-匈牙利 & 最大权匹配-KM)

《二分图及相关问题整理》

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

#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define maxn 1005
int n, m, c, ans;
vector <int> e[maxn];
int mtc[maxn], vis[maxn];

inline bool match(int u, int wh)
{
	if(vis[u] == wh) return 0;//避免死循环 
	vis[u] = wh;
	if(!e[u].size()) return 0;//注意边界 
	rep(i, 0, e[u].size() - 1)
		if(!mtc[e[u][i]] or match(mtc[e[u][i]], wh)) 
		{
			mtc[e[u][i]] = u;
			return 1;
		}
	return 0;
}

int main()
{
	scanf("%d %d %d", &n, &m, &c);
	rep(i, 1, c)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		e[u].push_back(v);	
	}	
	rep(i, 1, n)
		ans += match(i, i);
	printf("%d\n", ans);
	return 0;
}

网络流(大概不考,熟悉板子即可)

《网络流【网络最大流 + 最小网络最大流】》

分数规划(提高考)

欧拉图

以下内容皆出自 (LG)Marsrayd。

image

P7771 【模板】欧拉路径

#include <bits/stdc++.h>
using namespace std;
const int MAX=100010;
int n,m,u,v,del[MAX];
int du[MAX][2];//记录入度和出度 
stack <int> st;
vector <int> G[MAX];
void dfs(int now)
{
	for(int i=del[now];i<G[now].size();i=del[now])
	{ 
		del[now]=i+1;
		dfs(G[now][i]);
	}
	st.push(now);
}
int main()
{
	scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&u,&v),G[u].push_back(v),du[u][1]++,du[v][0]++;  
    for(int i=1;i<=n;i++) sort(G[i].begin(),G[i].end());
    int S=1,cnt[2]={0,0}; //记录
    bool flag=1; //flag=1表示,所有的节点的入度都等于出度,
    for(int i=1;i<=n;i++)
	{
        if(du[i][1]!=du[i][0]) flag=0;
        if(du[i][1]-du[i][0]==1/*出度比入度多1*/) cnt[1]++,S=i;
        if(du[i][0]-du[i][1]==1/*入度比出度多1*/) cnt[0]++;
    }
    if((!flag)&&!(cnt[0]==cnt[1]&&cnt[0]==1)) return !printf("No");
	//不满足欧拉回路的判定条件,也不满足欧拉路径的判定条件,直接输出"No" 
    dfs(S);
    while(!st.empty()) printf("%d ",st.top()),st.pop();
    return 0; 
}

最小生成树

Kruskal(生成树) & Prim 堆优化

kruskal:

#include<bits/stdc++.h>
using namespace std;

int n, m;
int father[5005];

struct node{
	int x, y;
	int w;
}a[200005];

int cmp(node x, node y)
{
	return x.w < y.w;
}

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

void unionn (int x, int y)
{
	father[find(y)] = find(x);
}

bool judge (int x, int y)
{
	if (find(x) == find(y))return 1;
	else return 0;
}

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)father[i] = i;
	for (int i = 1; i <= m; i++)
	{
		cin >> a[i].x >> a[i].y >> a[i].w;
	}
	sort (a + 1, a + m + 1, cmp);
	int tot = 0;
	int cnt = 0;
	for (int i = 1; i <= m; i++)
	{
		if (judge(a[i].x, a[i].y) == 0)
		{
			unionn (a[i].x, a[i].y);
			cnt += 1;
			tot += a[i].w;
		}
		if (cnt == (n - 1))break;
	}
	if (cnt != (n - 1))cout << "orz";
	else cout << tot;
	return 0;
}

prim:

//prim
 
#include<bits/stdc++.h>
using namespace std;
 
int n;
int a[105][105];//邻接矩阵 
int minn[105];//存放每次邻接边权最小值 
bool u[105];//判断是否在图中
int ttl = 0;//total,最终答案 
 
int main()
{
    cin >> n;
    for (int i  = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)cin >> a[i][j];
    memset(minn, 0x7f, sizeof(minn));
    memset(u, 1, sizeof(u));//皆不在图中 
    minn[1] = 0;
    for (int i = 2; i <= n; i++)
    {
        int k = 0;//寻找当前最小的一个蓝点 
        for (int j = 1; j <= n; j++)
        {
            if(u[j] && (minn[j] < minn[k]))k=j;//打擂台 
        }
        u[k] = false;//连入图中 
        for (int j = 1; j <= n; j++)//刷新其他蓝点的最小权值 
            if(u[j] && (a[k][j] < minn[j]))
                minn[j] = a[k][j];
    }
    for (int i = 1; i <= n; i++)ttl += minn[i];//计算总和 
    cout<<ttl;
    return 0;   
}

(严格)次小生成树

最大生成树

【选】最优比率、最小瓶颈生成树

LCA

《动态树之 Link Cut Tree》

P3690 【模板】动态树(Link Cut Tree).

#include<bits/stdc++.h>
using namespace std;

#define rep(i, a, b) for(register int i = a; i <= b; ++i)
#define ls t[x].ch[0]
#define rs t[x].ch[1]
const int maxn = 3e5 + 5;
int n, m;
struct node{
    int f, ch[2], sum, val;
    bool rev;
}t[maxn];

inline bool nrt(int p)
{
    int x = t[p].f;
    return (ls == p or rs == p);
}

inline void up(int x) {t[x].sum = t[ls].sum ^ t[rs].sum ^ t[x].val;}

inline void rv(int x) {swap(ls, rs); t[x].rev ^= 1;}

inline void dw(int x)
{
    if(!t[x].rev) return;
    if(ls) rv(ls); if(rs) rv(rs);
    t[x].rev = 0;
}

inline void rotate(int x)
{
    int y = t[x].f, z = t[y].f;
    int kx = (t[y].ch[1] == x), ky = (t[z].ch[1] == y), w = t[x].ch[!kx];
    if(nrt(y)) t[z].ch[ky] = x;
    t[x].ch[!kx] = y, t[y].ch[kx] = w;
    if(w) t[w].f = y;
    t[y].f = x, t[x].f = z;
    up(y), up(x);
}

inline void pushall(int x) {if(nrt(x)) pushall(t[x].f); dw(x);}

inline void splay(int x)
{
    pushall(x);
    while(nrt(x))
    {
        int y = t[x].f, z = t[y].f;
        if(nrt(y)) rotate((t[z].ch[1] == y) ^ (t[y].ch[1] == x) ? x : y);
        rotate(x);
    }
    up(x);
}

inline void access(int x)
{
    for(register int lst = 0; x; x = t[lst = x].f)
        splay(x), rs = lst, up(x);
}

inline void mkrt(int x) {access(x), splay(x), rv(x);}

inline int fndrt(int x)
{
    access(x), splay(x);
    while(ls) dw(x), x = ls;
    splay(x); return x;
}

inline void split(int x, int y) {mkrt(x), access(y), splay(y);}

inline void link(int x, int y) {mkrt(x); if(fndrt(y) != x) t[x].f = y;}

inline void cut(int x, int y)
{
    split(x, y);
    if(fndrt(y) == x and t[y].f == x and !t[y].ch[0])
        t[y].f = t[x].ch[1] = 0, up(x);
}

int main()
{
    scanf("%d%d", &n, &m);
    rep(i, 1, n) scanf("%d", &t[i].val);
    rep(i, 1, m)
    {
        int opt, x, y;
        scanf("%d%d%d", &opt, &x, &y);
        if(!opt) split(x, y), printf("%d\n", t[y].sum);
        if(opt == 1) link(x, y);
        if(opt == 2) cut(x, y);
        if(opt == 3) splay(x), t[x].val = y;
    }
    return 0;
}

基环树

树链剖分

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

#include<bits/stdc++.h>
using namespace std;

#define int long long
int n, m, rt;
int p;
const int maxn = 200005;

int cnt, hd[maxn];
struct node{
	int nxt, to;
}e[maxn * 2];

int wt[maxn], w[maxn];//new & old number's val
struct tree{
	int l, r;
	int lz,sum;
}t[4 * maxn];

int tmp;
int dep[maxn], fa[maxn];
int siz[maxn], son[maxn];
int top[maxn], id[maxn];

//------------------------------

inline void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

//------------------------------

inline void dfs1 (int x, int fth)
{
	dep[x] = dep[fth] + 1;
	fa[x] = fth;
	siz[x] = 1;
	int maxss = -1;
	for (int i = hd[x]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == fth) continue;
		dfs1 (v, x);
		siz[x] += siz[v];
		if (siz[v] > maxss)
		{
			maxss = siz[v];
			son[x] = v;
		}
	}
}

inline void dfs2 (int x, int tpx)
{
	id[x] = ++tmp;
	wt[tmp] = w[x];
	top[x] = tpx;
	if (!son[x]) return;
	dfs2 (son[x], tpx);
	for (int i = hd[x]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == fa[x] or v == son[x]) continue;
		dfs2 (v, v);
	}
}

//------------------------------

inline void build (int i, int l, int r)
{
	t[i].l = l, t[i].r = r;
	if (l == r)
	{
		t[i].sum = wt[l];
		if (t[i].sum > p) t[i].sum %= p;
		return;
	}
	int mid = (l + r) >> 1;
	build (i << 1, l, mid);
	build (i << 1 | 1, mid + 1, r);
	t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum;
	t[i].sum %= p;
	return;
}

inline void push_down (int i)
{
	if (t[i].lz)
	{
		t[i << 1].lz += t[i].lz;
		t[i << 1].lz %= p;
		t[i << 1 | 1].lz += t[i].lz;
		t[i << 1 | 1].lz %= p;
		//int lenn = t[i].r - t[i].l + 1;
		//t[i << 1].sum += (t[i].lz * (lenn - (lenn >> 1)));
		//t[i << 1 | 1].sum += (t[i].lz * (lenn >> 1));
		int lenl, lenr;
		lenl = t[i << 1].r - t[i << 1].l + 1;
		lenr = t[i << 1 | 1].r - t[i << 1 | 1].l + 1;
		t[i << 1].sum += (t[i].lz * lenl);
		t[i << 1 | 1].sum += (t[i].lz * lenr);
		t[i << 1].sum %= p;
		t[i << 1 | 1].sum %= p;
	}
	t[i].lz = 0;
	return;
}

inline void updt (int i, int l, int r, int k)
{
	 if (t[i].l >= l and t[i].r <= r)
	 {
	 	t[i].lz += k;
	 	int len = t[i].r - t[i].l + 1;
	 	t[i].sum += (k * len);
	 	t[i].sum %= p;
	 	return;
	 }
	 push_down (i);
	 if (t[i << 1].r >= l) updt (i << 1, l, r, k);
	 if (t[i << 1 | 1].l <= r) updt (i << 1 | 1, l, r, k);
	 t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum;
	 t[i].sum %= p;
	 return;
}

inline int query (int i, int l, int r)
{
	if (t[i].l >= l and t[i].r <= r) return t[i].sum % p;
	push_down (i);
	int s = 0;
	if (t[i << 1].r >= l) s += query (i << 1, l, r);
	if (t[i << 1 | 1].l <= r) s += query (i << 1 | 1, l, r);
	s %= p;
	return s;
}

//------------------------------

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

inline void updtrange (int x, int y, int k)
{
	k %= p;
	while (top[x] != top[y])
	{
		if (dep[top[x]] < dep[top[y]]) swap (x, y);
		updt (1, id[top[x]], id[x], k);
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap (x, y);
	updt (1, id[x], id[y], k);
}

inline int qson (int x)
{
	return query (1, id[x], id[x] + siz[x] - 1);
}

inline void uson (int x, int k)
{
	updt (1, id[x], id[x] + siz[x] - 1, k);
}

//------------------------------

signed main ()
{
	scanf ("%lld%lld%lld%lld", &n, &m, &rt, &p);
	for (int i = 1; i <= n; i++) scanf ("%lld", &w[i]);
	for (int i = 1; i < n; i++)
	{
		int u, v;
		scanf ("%lld%lld", &u, &v);
		add (u, v), add (v, u);
	}
	dfs1 (rt, 0);
	dfs2 (rt, rt);
	build (1, 1, n);
	while (m--)
	{
		int k, x, y, z;
		scanf ("%lld", &k);
		if (k == 1)
		{
			scanf ("%lld%lld%lld", &x, &y, &z);
			updtrange (x, y, z);
		}
		else if (k == 2)
		{
			scanf ("%lld%lld", &x, &y);
			printf ("%lld\n", qrange (x, y));
		}
		else if (k == 3)
		{
			scanf ("%lld%lld", &x, &y);
			uson (x, y);
		}
		else if (k == 4)
		{
			scanf ("%lld", &x);
			printf ("%lld\n", qson (x));
		}
	}
	return 0;
}

括号序列

dfs序

树上倍增

树的直径、树的重心

树的直径:

一下均摘自 OI-Wiki。

法1:使用两次 dfs。

const int N = 10000 + 10;

int n, d = 0;
int d1[N], d2[N];
vector<int> E[N];

void dfs(int u, int fa) {
  d1[u] = d2[u] = 0;
  for (int v : E[u]) {
    if (v == fa) continue;
    dfs(v, u);
    int t = d1[v] + 1;
    if (t > d1[u])
      d2[u] = d1[u], d1[u] = t;
    else if (t > d2[u])
      d2[u] = t;
  }
  d = max(d, d1[u] + d2[u]);
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i < n; i++) {
    int u, v;
    scanf("%d %d", &u, &v);
    E[u].push_back(v), E[v].push_back(u);
  }
  dfs(1, 0);
  printf("%d\n", d);
  return 0;
}

法2:树形 dp(最远距离和次远距离)。

const int N = 10000 + 10;

int n, d = 0;
int d1[N], d2[N];
vector<int> E[N];

void dfs(int u, int fa) {
  d1[u] = d2[u] = 0;
  for (int v : E[u]) {
    if (v == fa) continue;
    dfs(v, u);
    int t = d1[v] + 1;
    if (t > d1[u])
      d2[u] = d1[u], d1[u] = t;
    else if (t > d2[u])
      d2[u] = t;
  }
  d = max(d, d1[u] + d2[u]);
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i < n; i++) {
    int u, v;
    scanf("%d %d", &u, &v);
    E[u].push_back(v), E[v].push_back(u);
  }
  dfs(1, 0);
  printf("%d\n", d);
  return 0;
}

树的重心

image

// 这份代码默认节点编号从 1 开始,即 i ∈ [1,n]
int size[MAXN],  // 这个节点的“大小”(所有子树上节点数 + 该节点)
    weight[MAXN],  // 这个节点的“重量”
    centroid[2];   // 用于记录树的重心(存的是节点编号)

void GetCentroid(int cur, int fa) {  // cur 表示当前节点 (current)
  size[cur] = 1;
  weight[cur] = 0;
  for (int i = head[cur]; i != -1; i = e[i].nxt) {
    if (e[i].to != fa) {  // e[i].to 表示这条有向边所通向的节点。
      GetCentroid(e[i].to, cur);
      size[cur] += size[e[i].to];
      weight[cur] = max(weight[cur], size[e[i].to]);
    }
  }
  weight[cur] = max(weight[cur], n - size[cur]);
  if (weight[cur] <= n / 2) {  // 依照树的重心的定义统计
    centroid[centroid[0] != 0] = cur;
  }
}

数论

  • 埃氏筛、线性筛
  • 欧拉函数
    • 欧拉定理
    • 线性筛欧拉函数
    • sqrt(n)求单个值的欧拉函数
  • exgcd
    • 求逆元
    • 求同余方程
    • ax+by=c
  • 中国剩余定理-互质版
  • 矩阵快速幂
  • 容斥原理-Ramsey定理
  • 费马小定理
  • 逆元(线性求、exgcd求、费马小定理求)
  • 高斯消元
  • 线性基
  • 排列组合-杨辉三角
  • 斐波那契数列-gcd(fn,fm)=f[gcd(n,m)]
  • 卡特兰数
  • 【选】斯特林数、贝尔数
  • 概率 & 期望

动态规划

  • 简单 dp
  • 背包 dp
    • 01 背包
    • 完全背包
    • 多重背包
  • 区间 dp
  • 状压 dp (普通状压 & 枚举子集 dp)
  • 数位 dp
  • 树形 dp (基环树 dp)
  • 环形 dp
  • 环 + 外向树上的 dp
  • 期望 dp
  • dp 套 dp
  • 优化
    • 【选】四边形不等式
    • 单调队列、线段树

数据结构

  • 带权并查集
  • 哈希表
  • 双向列表
  • st 表
  • 树状数组 (求逆序对 & 多维)
  • 线段树
    • 动态开点 & 线段树的合并
    • 权值线段树
    • 【选】zkw 线段树
    • 二维线段树
    • 主席树 (静态 & 动态 第 k 大)
    • 扫描线
  • 平衡树 (splay、treap、无旋 treap)
  • trie 树
  • STL((multi)set)

搜索

  • bfs/dfs
  • 双向 bfs

其他算法思想

  • 二分、三分
  • 倍增
  • 贪心
  • 分治
  • 离散化
  • 模拟
  • 排序(重载运算符)
  • 分块
  • (二维)前缀和
  • (压位)高精度
  • 矩阵加速递推
  • 打表
posted @   pldzy  阅读(159)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示