ACM模板

本文涵盖了多种数据结构和算法的应用,包括深度优先搜索(DFS)、广度优先搜索(BFS)、哈夫曼编码、模拟退火、欧拉函数、快速幂、线性筛、网络流、二分图匹配等。讨论了如何在有限空间内进行搜索,以及如何在有向图中找到最短路径、最长路径、最小生成树、最大流等问题。此外,还介绍了如何使用动态规划和线性基解决数学问题,如中国剩余定理和线性求逆。最后,探讨了字符串哈希、康托展开、KMP算法、AC自动机和数据结构如并查集、ST表和线段树的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

搜索

DFS

IDA*搜索
迭代加深 + 启发式搜索
迭代加深适用于没有大量空间供BFS 所以一层一层搜最小答案
启发式搜索加上一个启发式函数,可以剪枝大量不必要的答案

int DFS(int dep, int px, int py)  //因为是迭代加深 只需要知道当前答案如果是mx时 有没有答案,return 0 或者1 即可
{
	if(dep == mx) 
	{
		if(Check()) return 1;
		return 0;
	}
	for(int nx, ny, i = 1; i <= 8; i++)
	{
		nx = px + dx[i]; ny = py + dy[i]; 
		if(1 > nx || nx > 5 || 1 > ny || ny > 5) continue;
		SWAP(mp[px][py], mp[nx][ny]); 
		int eva = eval(); //估价函数 例如扫描原图与当前图不同的点数目 曼哈顿距离 即可能的最理想剩余要搜索的深度
		if(eva + dep <= mx) //只有在最优的情况也可以搜到 才去搜索
			if(DFS(dep+1, nx, ny)) return 1;
		SWAP(mp[px][py], mp[nx][ny]); //别忘了回溯
	}
	return 0;
}
int main()
{
	for(int i = 1; i <= 15; i++)
	{
		mx = i;
		if(DFS(0, px, py)){ans = i; break;}
	}
 } 

BFS

双向BFS

//要求是有起点终点状态的,把起点终点放进两个队列,而且每次bfs取延伸少的一边,会更快 
//这个题需要两个map存从两边走的状态,如果相遇了,答案=两边路程之和-1
struct node
{int x, y;}p[4];
init();
q1.push(s);q2.push(goal);
while(!q1.empty() && !q2.empty()) //这个题队列只增不减, 有的需要判断一下队列空了没 
{
	string tp;
	int sz1 = q1.size(), sz2 = q2.size(), flag;
	if(sz1 < sz2)	{tp = q1.front();q1.pop();flag = 1;}
	else	{tp = q2.front();q2.pop();flag = 2;}
	node ndt = GetP(tp);
	for(int i = 0; i < 4; i++)
	{
		node ndn; ndn.x = ndt.x + p[i].x; ndn.y = ndt.y + p[i].y;
		if(!Check(ndn)) continue;
		string ss = GetS(ndt, ndn, tp);
		if(flag == 1)
		{
			if(mp1[ss]) continue; //如果走回去了
			if(mp2[ss] != 0/*注意不能写 !mp2[ss] 因为这样也是真必须判断不为0 初始值赋值为1 最后再减*/) 
			{
				printf("%d\n", mp1[tp] + mp2[ss] - 1);//如果在另一边bfs到了  
				return 0;
			}
			mp1[ss] = mp1[tp] + 1;
			q1.push(ss);
		}
		else
		{
			if(mp2[ss]) continue;
			if(mp1[ss] != 0)
			{
				printf("%d\n", mp1[ss] + mp2[tp] - 1);
				return 0;
			}
			mp2[ss] = mp2[tp] + 1;
			q2.push(ss);
		}
	}
}

优先队列BFS
两个状态之间不一定为1的距离时求最短路径

哈夫曼编码

//求用ASCII码和哈夫曼码需要的长度和比值
//优先队列BFS模拟建哈夫曼树的过程
//路径:根节点到叶子节点的距离
//带权路径:路径×叶子节点权值
//哈夫曼树:带权路径最小的树
//——————————————建树过程—————————————————
//把所有点丢到优先队列里
//每次取权值最小的两个点,他俩为叶子节点生成一个父亲节点丢到优先队列里
//树的带权路径和加上这两个点的权值 
//(因为最开始的叶子节点每次都会在上一层父亲节点生成新节点时又加一遍,加的次数就是它的深度,保证求的就是树的带权路径和)
const int N = 1e5+7;
string s;
int sum, len, ans, cnt[27];
int main()
{
    while(cin >> s)
    {
        rep(i, 0, 26)
            cnt[i] = 0;
        sum = ans = 0;
        if(s == "END")
            break;
        len = s.size();
        rep(i, 0, len-1)
        {
            if(s[i] == '_')
                cnt[26]++, sum++;
            else
                cnt[s[i]-'A']++, sum++;
        }
        priority_queue <int, vector<int>, greater<int> > pq;
        rep(i, 0, 26) if(cnt[i]) pq.push(cnt[i]);
        if(pq.size() == 1) ans = pq.top();
        while(pq.size() > 1)
        {
            int t1 = pq.top(); pq.pop();
            int t2 = pq.top(); pq.pop();
            pq.push(t1 + t2);
            ans += t1 + t2;
        }
        cout << sum * 8 << ' ' << ans << ' ' << setprecision(1) <<  (double)sum*8/ans << endl;
    }
    return 0;
}

模拟退火

void SimulateAnneal()
{ 
	for(long double t = 1e5; t > ESP; t *= delta) 
	{	
		long double xtmp = xx + t*(rand()*2 - RAND_MAX); //随机跳的值
		long double ytmp = yy + t*(rand()*2 - RAND_MAX);
		long double res = Check(xtmp, ytmp);
		if(res < best) best = res, xans = xtmp, yans = ytmp;
		if(ans > res || exp(ans-res/t) > (long double)rand()/RAND_MAX)  ans = res, xx = xtmp, yy = ytmp;
	}
}
int main()
{
	//!!!注意 ans, best 一定要有合理的初始值 不然一开始跳的很大直接不知道跳哪了
	xans /= n; yans /= n;
	xx = xans, yy = yans;
	best = ans = Check(xans, yans);
	SimulateAnneal();
	int timepersa = clock(); //记录每次的时间
	while(clock() < CLOCKS_PER_SEC*0.95 - timepersa) SimulateAnneal();
}

数论

欧拉函数

log级求一个数的欧拉函数

LL Euler(LL x)
{
    LL res = x;
    for(int i = 2; i*i <= x; i++)
    {
        if(x % i == 0)
        {
            res = res - res/i;
            while(x % i == 0) x /= i;
        }
    }
    if(x > 1) res = res - res/x;
    return res;
}

如果求 1-n 中gcd(n, x) >= k 的数的个数,就根号n枚举最大公约数,然后加上 euler(n/最大公约数),注意每次有俩。
线性筛欧拉函数

void PhiTable(int n)
{
    phi[1] = 1;
    for(int i = 2; i <= n; i++)
    {
        if(!phi[i])
        {
            for(int j = i; j <= n; j += i)
            {
                if(!phi[j]) phi[j] = j;
                phi[j] = phi[j] / i * (i - 1);
            }
        }
    }
}

快速幂

整数快速幂

LL FastPower(LL a, LL x)
{
	LL res = 1;
	while(x)
	{
		if(x & 1) res = (res * a)%MOD;
		a = (a*a)%MOD, x >>= 1;
	}
	return res;
}

欧拉降幂
A K ≡ A K % ϕ ( B ) + ϕ ( B ) ( mod  B ) A^K\equiv A^{K\%\phi(B)+\phi(B)}(\text{mod}\ B) AKAK%ϕ(B)+ϕ(B)(mod B)

LL EulerPower(LL a, string b) //b以字符串形式读入
{
    LL len = b.size(), p = Euler(MOD), ans = 0;
    rep(i, 0, len-1) ans = (ans*10 + b[i]-'0') % p;
    ans += p;
    return FastPower(a, ans);
}

矩阵快速幂

struct Matrix
{
    LL m[MAXN][MAXN];
    Matrix(){memset(m, 0, sizeof(m));}
};
Matrix Mul(Matrix a, Matrix b)
{
    Matrix res;
    rep(i, 1, n) rep(j, 1, n) rep(k, 1, n) res.m[i][j] = (res.m[i][j] + a.m[i][k] * b.m[k][j]) % mod;
    return res;
}
Matrix MPower(Matrix a, LL b)
{
    Matrix res;
    rep(i, 1, n) res.m[i][i] = 1;
    while(b)
    {
        if(b&1) res = Mul(res, a);
        a = Mul(a, a);
        b >>= 1;
    }
    return res;
}

拓展欧几里得算法

//有 ax + by = gcd(a, b)的一个解 (x, y) 
LL ex_gcd(LL a, LL b, LL &x, LL &y)
{
	if(b == 0){ x = 1, y = 0; return a; }
	ll d = ex_gcd(b, a%b, x, y), tmp = x;
	x = y, y = tmp - (a/b) * y;
	return d; 
}
/*对于 ax + by = n 的整数解
  1.判断 n是不是 gcd(a, b)倍数 不是无解
  2.用ex_gcd求一个x0, y0
  3.解为 x = x0 * n / gcd(a, b) y = y0 * n / gcd(a, b) */

求逆元

费马小定理 模质数 p p p 时, x x x 的逆元为 x p − 2 x^{p-2} xp2,用个快速幂
欧拉定理 模数 p p p 不是质数时, x x x 的逆元为 x ϕ ( p ) − 1 x^{\phi(p)-1} xϕ(p)1,用个欧拉函数和快速幂
exgcd

LL mod_reverse(LL a, LL b)
{
    LL d, x, y;
    d = ex_gcd(a, b, x, y);
    if(d == 1) return (x%b+b)%b;
    else return -1;
}

线性求逆元

//inv[x] x的逆元 模P
inv[1] = 1;
rep(i, 2, N) inv[i] = (LL)(P-P/i)*inv[P%i]%P;

中国剩余定理

x {x} x a i {a_i} ai( m o d mod mod m i {m_i} mi )
如果题目给的不是 x % a i = m i x\%a_i = m_i x%ai=mi 而是 x ≡ a i ( mod  m i ) x\equiv a_i(\text{mod} \ m_i) xai(mod mi) 记得提前 a[i] %= m[i]
m两两互质

LL CRT(int n)
{
    LL M = 1, res = 0;
    rep(i, 1, n) M *= m[i];
    rep(i, 1, n)
    {
        LL x, y, tm = M/m[i];
        ex_gcd(tm, m[i], x, y);
        res = (res + tm * x * a[i]) % M;
    }
    return (res + M) % M;
}

m不互质

LL ex_CRT(LL n)
{
    if (n == 1)
    {
        if(m[1] > a[1]) return a[1];
        else return -1;
    }
    LL x, y, d;
    rep(i, 2, n)
    {
        if (m[i] <= a[i]) return -1;
        d = ex_gcd(m[1], m[i], x, y);
        if ((a[i] - a[1]) % d != 0) return -1;
        LL t = m[i] / d;
        x = ((a[i] - a[1]) / d * x % t + t) % t;
        a[1] = x * m[1] + a[1];
        m[1] = m[1] * m[i] / d;
        a[1] = (a[1] % m[1] + m[1]) % m[1];
    }
    return a[1];
}

欧拉筛

rep(i, 2, n)
{
	if(!vis[i]) prime[++cnt] = i;
	for(int j = 1; j <= cnt && i * prime[j] <= n; j++)
	{
		vis[i*prime[j]] = 1;
		if(i % prime[j] == 0) break;
	}
}
```	vis[j] = 1; 

小常数判断质数

int IsPrime(LL n) 
{ 
	LL stop = n / 6 + 1, Tstop = sqrt(n) + 5;
	if (n == 2 || n == 3 || n == 5 || n == 7 || n == 11) { return 1;}
	if (n % 2 == 0 || n % 3 == 0 || n % 5 == 0 || n == 1) { return 0;}
	for (unsigned long long i = 1; i <= stop; i++) 
    {
		if (i * 6 >= Tstop) break;
		if ((n % (i * 6 + 1) == 0) || (n % (i * 6 + 5) == 0))return 0;
	}
	return 1;
}

线性基

int d[64], a[N], n, tot;
//线性基插入元素
void Add(LL x)
{
    per(i, 62, 0)
    {
        if(x&(1LL<<i))
        {
            if(d[i]) x ^= d[i];
            else
            {
                d[i] = x, tot++;
                break;
            }
        }
    }
}
//求最大值
LL Maxn()
{
    LL maxn = 0;
    per(i, 62, 0) if((maxn ^ d[i]) > maxn) maxn ^= d[i];
    return maxn;
}
//求最小值
LL Minn()
{
    if(tot < n) return 0;
    rep(i, 0, 62) if(d[i])
    return d[i];
}
//化简线性基 如果求第k小是必要的 上面求最大最小的方法不用化简
void Simp()
{
    rep(i, 1, 62)
        rep(j, 1, i)
            if(d[i] & (1LL << (j-1)))
                d[i] ^= d[j-1];
}
//求第k小的数 如果没有这么多数 返回-1
LL KMinn(LL k)
{
    if(tot < n) k--;
    Simp();
    int cnt = 0;
    rep(i, 0, 62) if(d[i]) cnt++;
    if(k > (1LL<<cnt)-1) return -1;
    LL minn = 0;
    rep(i, 0, 62)
    {
        if(d[i])
        {
            if(k%2) minn ^= d[i];
            k >>= 1;
        }
    }
    return minn;
}

母函数

//多项式乘法
while(cin >> n && n)
{
    rep(i, 0, n) c1[i] = 1, c2[i] = 0;
    rep(i, 2, 17)
    {
        for(int j = 0; j <= n; j += i)
            for(int k = 0; k <= n; k++)
                c2[j+k] += c1[k];
        rep(i, 0, n) c1[i] = c2[i], c2[i] = 0;
    }
    cout << c1[n] << endl;
}

图论

拓扑排序

//输出字典序最小的拓扑排序 bfs+优先队列 如果不是字典序最小而是号小尽量靠前就反着字典序最大
priority_queue <int> pq;
for(int i = 1; i <= n; i++) if(!ind[i]) pq.push(i);
while(!pq.empty())
{
	int u = pq.front();
	pq.pop(); cnt++; //cnt计数用的  若结束后cnt != n 说明不是DAG
	cout << u << endl;
	for(auto v : g[u]) if(!--ind[v]) pq.push(v)
}

欧拉路

先判断是不是连通图
1.无向:
(1)欧拉回路:全部点度数为偶数,可以从任意点出发返回这个点。
(2)欧拉路:只有两个奇点,从一个点到另一个点。
2.有向(入度为1出度-1度数为代数和):
(1)欧拉回路:所有点度数为0。
(2)欧拉路:一个起点度数为1,一个终点度数为-1。

//无向图输出欧拉路/欧拉回路 如果是有向图,就不用删另一个边,而且起点终点判断不太一样
//先判断度来找起点,终点
//一定要这么写, 不能it++, 因为删了边,地址变了, 而且不用写vis,写了vis continue会死循环
void dfs(int u)
{
	for(multiset<int>::iterator it = ms[u].begin(); it != ms[u].end(); it = ms[u].begin())
	{
        int v = *it; //删边一定要用迭代器删 只删一条边
		ms[u].erase(it);
		//有向图去掉下面这句
		ms[v].erase(ms[v].find(u));
		dfs(v);
	}
	ans[++cnt] = u;
}
//找起点 即度为奇数的
for(int i = 1; i <= n; i++)
{
	if(d[i]%2)
	{
		dfs(i);
		flag = 1;
		break;
	}
}
//如果都是偶数 选最小的点为起点
if(!flag)
    dfs(1);
//倒序输出路径
for(int i = cnt; i ; i--) cout << ans[i] << endl;

判断割边 割点

/*  dfs树的根节点如果有两个以上儿子节点那么根是割点 
 	其他节点如果存在一个子节点不会回到这个节点以上的点,那么这个节点也是割点*/	 
void dfs(int u, int f)
{
	int cnt = 0, sz = G[u].size();
	num[u] = low[u] = ++dfn; //num为实际dfs层数 low是从这开始能延伸到最浅的层数 判断子节点有没有回去 
	for(int i = 0; i < sz; i++)
	{
		int v = G[u][i];
		if(!num[v])
		{
			cnt++;
			dfs(v, u);
			low[u] = min(low[u], low[v]); 
			if(low[v] >= num[u] && u != rot) //有的题目可能会有重边,记得去重
				iscut[u] = 1;
			//如果是求割边 上面if改为如下 统计iscut个数就是割边的数量 
			//if(low[v] > num[u] && u != rot) iscut[u] = 1; 
		}
		else if(num[v] < num[u] && v != fa)
			low[u] = min(low[u], num[v]);
	}
	if(u == rot && cnt >= 2)
		iscut[u] = 1;
}

点双连通分量、边双连通分量

强连通分量、缩点
联通:无向图中任意点之间可以相互到达
强连通:有向图中,任意点之间可以相互到达
弱联通:把有向图看作无向图时,任意点之间可以相互到达

缩完点后的图一般只关注两种点,出度为零的点和入度为零的点。
如果要再把这个图补成一个强连通图,只需添加max(入度为零的点数,出度为零的点数)条边。
特别地,如果只有一个点,即原图已经是强连通图了,那就不用变了。

Kosaraju

// 对原图DFS一遍得到一个栈序 按栈序逆序DFS 每次DFS的块就是缩点的块
int n, m, f[N], vis[N], e[N][2];
//G1原图 G2反图 G3缩点图 用了一个vis数组 一开始vis = 0表示没搜过 第二次 vis = 1表示没搜过
vector <int> G1[N], G2[N], G3[N], s;
//对原图DFS
void DFS1(int x)
{
    vis[x] = 1;
    for(int i : G1[x]) if(!vis[i]) DFS1(i);
    s.pb(x);
}
//对反图按栈逆序DFS每个块
void DFS2(int x, int tag)
{
    f[x] = tag, vis[x] = 0;
    for(int i : G2[x]) if(vis[i]) DFS2(i, tag);
}
int main()
{
    cin >> n >> m;
    rep(i, 1, m)
    {
        cin >> e[i][0] >> e[i][1];
        G1[e[i][0]].pb(e[i][1]);
        G2[e[i][1]].pb(e[i][0]);
    }
    rep(i, 1, n) if(!vis[i]) DFS1(i);
    int sz = s.size();
    per(i, sz-1, 0) if(vis[s[i]]) DFS2(s[i], s[i]);
    //建缩点新图
    rep(i, 1, m) if(f[e[i][0]] != f[e[i][1]]) G3[f[e[i][0]]].pb(f[e[i][1]]);
    return 0;
}

Tarjan

int main()
{
	for(int i = 1; i <= m; i++) scanf("%d %d", &x[i], &y[i]); //方便等下再建图
	for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i);
	//建立新图缩点 
	for(int i = 1; i <= m; i++) if(color[x[i]] != color[y[i]]) G[color[x[i]]].push_back(color[y[i]]); 
 } 
void tarjan(int u)
{
	dfn[u] = low[u] = ++dep, instack[u] = 1, stack[++top] = u;
	for(int i = head[u]; i; i = edge[i].nxt)
	{
		int v = edge[i].to;
		if(!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[v], low[u]);
		}
		else if(instack[v]) //在栈里才需要判断 因为不然可能就是别的连通块了
			low[u] = min(low[u], low[v]);
	}
	
	if(low[u] == dfn[u]) //说明这个节点是连通分量dfs树上的根了 开始把这块整出来 
	{
		color[u] = ++tot; //sum为连通分量总数
		instack[u] = 0; //出栈
		while(stack[top] != u)
		{
			color[stack[top]] = tot;
			instack[stack[top--]] = 0;
			sum[tot] += val[u]; //缩点,把强连通分量内的点权相加 
		}
		top--; 
	}
}

最短路 / 最长路

Floyd

n ≤ 200 n \leq 200 n200 时可以判断负环,可以求最长路,初始化为0

//有向图可以输出最短路路径
memset(g, INF, sizeof(g));
cin >> n >> m;
int t1, t2, t3;
rep(i, 1, m)
{
    cin >> t1 >> t2 >> t3;
	g[t1][t2] = t3, p[t1][t2] = t2;
}
rep(k, 1, n)
	rep(i, 1, n)
		if(g[k][i] != INF) //优化 
			rep(j, 1, n)
				if(g[i][k] + g[k][j] < g[i][j]) g[i][j] = g[i][k] + g[k][j], p[i][j] = p[i][k];
//输出路径 u->v
cout << u << ' ';
while(p[u][v] != v)
{
	cout << p[u][v] << ' ';
	u = p[u][v];
}
cout << v << endl;
//判断负环
for(int i = 1; i <= n; i++)
	if(g[i][i] < 0)

SPFA

数据大 而且可能有负环时用
可以求最长路,边权初始化为相反数,求得最短路的相反数就是最长路

int n, neg[N], inq[N], dis[N], pre[N];
vector <pair<int, int>> G[N];
queue <int> q;
int SPFA(int s)
{
	rep(i, 1, n) if(i != s) dis[i] = INF;
	q.push(s);
	inq[s] = 1;
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		inq[u] = 0;
        for(auto i : G[u])
		{
			int v = i.fr, w = i.se;
			if(dis[u] + w < dis[v])
			{
				dis[v] = dis[u] + w, pre[v] = u; //记录路径
				if(!inq[v])
				{
					q.push(v);
					inq[v] = 1;
					neg[v]++;
					if(neg[v] > n) return 0;//有负环
				}
			}
		}
	}
}

差分约束

Dijkstra

无负边 稳定单源最短路
注意优先队列是当前状态的dis,不能直接比dis[x] > dis[b.x]

struct edge
{
	int f, t, w, nxt;
}edge[N];
struct node
{
	int id, dis;
	node(int a, int b)
	{id = a; dis = b;}
	bool operator < (const node & b)const
	{return dis > b.dis; }
};
void AddEdge(int u, int v, int w)
{
	edge[++cnt].nxt = head[u];
	head[u] = cnt;
	edge[cnt].f = u;
	edge[cnt].t = v;
	edge[cnt].w = w;
}
void Dijkstra()
{
	for(int i = 1; i <= n; i++)
		dis[i] = INF;
	dis[s] = 0;
	priority_queue <node> pq;
	pq.push(node(s, dis[s]));
	while(!pq.empty())
	{
		node nd = pq.top();	int u = nd.id;
		pq.pop();
		if(done[u])
			continue;
		done[u] = 1;
		for(int i = head[u]; i; i = edge[i].nxt)
		{
			int v = edge[i].t;
			if(done[v])
				continue;
			if(dis[v] > dis[u] + edge[i].w)
			{
				dis[v] = dis[u] + edge[i].w;
				pq.push(node(v, dis[v]));
				pre[v] = u; //打印路径 
			}
			
		}
	}
}

int main()
{
	scanf("%d %d %d", &n, &m, &s);
	for(int t1, t2, t3, i = 1; i <= m; i++)
	{
		scanf("%d %d %d", &t1, &t2, &t3);
		AddEdge(t1, t2, t3);
	}
	Dijkstra();
	for(int i = 1; i <= n; i++)
		cout << dis[i] << ' ';
	cout << endl;
	return 0; 
}
//用vector pair存图 pair优先队列维护
vector <pair<int, int>> G[N];
struct cmp
{
    bool operator()(const pair<int, int> p1,const pair<int, int> p2)
    {return p1.first > p2.first;}
};
void Dijkstra(int x)
{
    rep(i, 0 , n) dis[i] = INF, vis[i] = 0;
    dis[x] = vis[x] = 0;
    priority_queue <pair<int, int>, vector<pair<int, int>>, cmp> pq;
    pq.push(mp(0, x));
    while(!pq.empty())
    {
        int u = pq.top().second; pq.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(auto it = G[u].begin(); it != G[u].end(); it++)
        {
            int v = it->first, l = it->second;
            if(vis[v]) continue;
            if(dis[v] > dis[u] + l)
            {
                dis[v] = dis[u] + l;
                pq.push(mp(dis[v], v));
            }
        }
    }
}

最小生成树

int findf(int x)
{
	if(x != f[x]) f[x] = findf(f[x]);
	return f[x];
} 

cin>>n>> m;
for(int i = 1; i <= n; i++) f[i] = i;
for(int t1, t2, t3, i = 1; i <= m; i++)
{
    scanf("%d %d %d", &t1, &t2, &t3);
    edge[i].u = t1; edge[i].v = t2; edge[i].w = t3;
}
sort(edge + 1, edge + m + 1, cmp);
for(int i = 1; i <= m; i++)
{
    int f1 = findf(edge[i].u);
    int f2 = findf(edge[i].v);
    if(f1 == f2) continue;
    f[f1] = f2;
    ans += edge[i].w;
}
int rot = findf(1); //判断一下是不是联通的 
for(int i = 2; i <= n ; i++)
    if(findf(i) != rot)
    {
        cout << "orz\n";
        return 0;
    }
cout << ans << endl;

n小生成树

最大曼哈顿距离

int x[M], y[M], p[4][M];
int main()//可以拓展到n维,把一个点各维度正负号所有2^k种情况存起来
{
  	cin>>n;
  	rep(i, 1, n)
  	{
  		cin>>x[i]>>y[i];
  		p[0][i] = x[i]+y[i];
  		p[1][i] =-x[i]+y[i];
  		p[2][i] = x[i]-y[i];
  		p[3][i] =-x[i]-y[i];
	}
	rep(i, 0, 3) sort(p[i]+1, p[i]+n+1); //不排序也行,记录最大最小即可
    rep(i, 0, 3) ans = max(ans, p[i][n]-p[i][1]);
	return 0;
}

树链剖分

树剖+线段树
可以求LCA,求树上路径上权值和,修改一段路径的权值,求子树权值,修改子树权值。

const int INF = 0x3f3f3f3f;
const int N = 1e5+7;
int T, n, head[N], cnt, idcnt, m, dep[N], fa[N], siz[N], son[N], r, top[N], id[N], w[N], nw[N];
//cnt是链式前向星存图记录边 idcnt是第二遍DFS时记录树上点的新编号用 只有线段树上是用的新节点
//son[x] x的重儿子
//top[x] x所在重链的顶端节点
//id[x] x节点新编号
//w[x] x节点原来权值
//nw[x] 新编号为x的节点的权值
vector <int> G[N];//vector存图
LL mod = (LL)1<<62;
struct SegTree
{LL val, add;}t[N<<2];
struct edge
{int u, v, dis, nxt;}e[N];
//如果初始边权,先用链式前向星存图后DFS0转化为vector存的图
void AddEdge(int u, int v, int dis)
{
    e[++cnt].nxt = head[u];
    e[cnt].dis = dis;
    e[cnt].v = v;
    head[u] = cnt;
}
void DFS0(int x, int f)
{
    for(int i = head[x]; i; i = e[i].nxt)
    {
        int to = e[i].v;
        if(to == f) continue;
        w[to] = e[i].dis;
        G[to].pb(x);
        G[x].pb(to);
        DFS0(to, x);
    }
}
//第一遍预处理 求fa dep siz son 数组
void DFS1(int x, int f)
{
    dep[x] = dep[f]+1, fa[x] = f, siz[x] = 1, son[x] = 0;
    int maxson = 0;
    for(int i : G[x])
    {
        if(i == f) continue;
        DFS1(i, x);
        siz[x] += siz[i];
        if(siz[i] > maxson)son[x] = i, maxson = siz[i];
    }
}
//第二遍预处理 求top id nw 数组
void DFS2(int x, int topf)
{
    id[x] = ++idcnt, nw[idcnt] = w[x], top[x] = topf;
    if(!son[x]) return;
    DFS2(son[x], topf);
    for(int i : G[x])
    {
        if(i == fa[x] || i == son[x]) continue;
        DFS2(i, i);
    }
}
//线段树不必初始化,每次Build就行 一堆数组不必初始化,son DFS会初始化,其余会覆盖,0节点一直为0
void init()
{
    rep(i, 1, n)
    {
        G[i].clear();
        head[i] = 0;
    }
    cnt = idcnt = 0;
}
//线段树板子搬过来用就行
#define ls rt<<1
#define rs rt<<1|1
void PushUp(int rt)
{
    t[rt].val =(t[ls].val + t[rs].val)%mod;
}
void Build(int l, int r, int rt)
{
    t[rt].add = 0;
	if(l == r)
    {
    	//注意是nw,因为要用一段连续的
        t[rt].val = nw[l];
        return;
    }
	int mid = (l+r)>>1;
	Build(l,   mid, ls);
	Build(mid+1, r, rs);
	PushUp(rt);
}

void PushDown(int rt, int ln, int rn)
{
	t[ls].val = (t[ls].val + t[rt].add * ln) % mod;
	t[rs].val = (t[rs].val + t[rt].add * rn) % mod;
	t[ls].add = (t[ls].add + t[rt].add) % mod;
	t[rs].add = (t[rs].add + t[rt].add) % mod;
	t[rt].add = 0;
}

void Add(int L, int R, int x, int l, int r, int rt)
{
	if(L <= l && r <= R)
	{
		t[rt].val = (t[rt].val + x*(r-l+1)) % mod;
		t[rt].add = (t[rt].add + x) % mod;
		return;
	}
	int mid = (l+r) >> 1;
	PushDown(rt, mid-l+1, r-mid);
	if(L <= mid) Add(L, R, x, l,   mid, ls);
	if(R > mid) Add(L, R, x, mid+1, r, rs);
	PushUp(rt);
}
LL Query(int L, int R, int l, int r, int rt)
{
	LL res = 0;
	if(L <= l && r <= R) return t[rt].val % mod;
	int mid = (l+r) >> 1;
	PushDown(rt, mid-l+1, r-mid);
	if(L <= mid) res = (res + Query(L, R, l,   mid, ls)) % mod;
	if(R > mid) res = (res + Query(L, R, mid+1, r, rs)) % mod;
	return res;
}
//如果不在同一条重链,让一直让重链顶端深度更深的点跳到父亲节点
//while结束后,就在一条重链上,深度浅的点为lca
int LCA(int u, int v)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    if(dep[u] > dep[v]) swap(u, v);
    return u;
}
//求u到v的最短路径上点权和 原理和求lca是一样的
//每次跳一条重链时,加上这段连续编号的区间和 【id[top[u]], id[u]】最后加上在同一条重链时的一段
LL QueryDis(int u, int v)
{
    LL ans = 0;
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        ans += Query(id[top[u]], id[u], 1, n, 1);
        ans %= mod;
        u = fa[top[u]];
    }
    if(dep[u] > dep[v]) swap(u, v);
    //如果 初始为边权 要减去lca 即此刻u的值
    //ans = ans - w[u];
    ans += Query(id[u], id[v], 1, n, 1);
    return ans % mod;
}
//u到v的最短路径上的点都加上val 同上,直接用线段树的Add代替Query
void AddDis(int u, int v, int val)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        Add(id[top[u]], id[u], val, 1, n, 1);
        u = fa[top[u]];
    }
    if(dep[u] > dep[v]) swap(u, v);
    Add(id[u], id[v], val, 1, n, 1);
}
//在u及其子树节点加上val
//因为新的节点编号是连续的【id[u], id[u]+siz[u]-1】 直接加即可
void AddSon(int u, int val)
{
    Add(id[u], id[u]+siz[u]-1, val, 1, n, 1);
}
//求u及其子树节点权值和 同上
LL QuerySon(int u)
{
    return Query(id[u], id[u]+siz[u]-1, 1, n, 1);
}
int main()
{
    fastio
    while(cin >> n >> m >> r >> mod)
    {
        init();
        int u, v, dis, opt, val;
        //如果是边权 用以下化为1为根 其他边权存在点上
//        rep(i, 1, n-1)
//        {
//            cin >> u >> v >> dis;
//            AddEdge(u, v, dis);
//            AddEdge(v, u, dis);
//        }
//        DFS0(1, 0);
        //如果点权 用以下直接做即可
        rep(i, 1, n) cin >> w[i];
        rep(i, 1, n-1)
        {
            cin >> u >> v;
            G[u].pb(v);
            G[v].pb(u);
        }
        //两边预处理以及建树
        DFS1(r, 0);
        DFS2(r, r);
        Build(1, n, 1);
        rep(i, 1, m)
        {
            cin >> opt;
            if(opt == 1)
            {
                cin >> u >> v >> val;
                AddDis(u, v, val);
            }
            else if(opt == 2)
            {
                cin >> u >> v;
                cout << QueryDis(u, v) << endl;
            }
            else if(opt == 3)
            {
                cin >> u >> val;
                AddSon(u, val);
            }
            else if(opt == 4)
            {
                cin >> u;
                cout << QuerySon(u) << endl;
            }
        }
    }
    return 0;
}

倍增求LCA

//预处理跑一遍dfs 
void DFS(int u, int fa)
{
	//f[u][i]表示为u向上2^i层的节点 
	d[u] = d[fa] + 1;
	f[u][0] = fa; 
	for(int i = 1; i <= 30; i++) //这个预处理要在这写 因为dfs到这个点的时候,他前面的点都更新完了,这个点更新f数组需要用到前面的点 
		f[u][i] = f[f[u][i-1]][i-1];
	for(int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].t;
		if(v == fa) continue;
		DFS(v, u);
	}
}
int LCA(int u1, int u2) //保证u1深度大于等于u2 
{
	if(d[u1] < d[u2])
	{int tmp = u1; u1 = u2; u2 = tmp;}
	int deltad = d[u1]- d[u2];
	for(int i = 0; i <= 30; i++)
	{
		if(deltad & (1<<i)) //二进制如果这位有数 就可以跳 2^i 层 所以这里顺序其实无所谓 
			u1 = f[u1][i];
	}

	if(u1 == u2) return u1; //这里是从大到小,根据所有数能表示为2^i之和,可以保证一定能跳到同一父亲节点 
	for(int i = 30; i >= 0; i--)
	{
		int f1 = f[u1][i];
		int f2 = f[u2][i];
		if(f1 != f2)
		{
			u1 = f1;
			u2 = f2;
		}
	}
	return f[u1][0]; //因为到最后一次跳 u1也不等于u2 他们就刚好差一层了 
}

树的直径

两种方法求
①两遍DFS,第一遍从任意点跑到离它最远的点,从这个点开始再次DFS到另一个最远的点,这俩点之间路径就是树的直径(可以用反证法证明正确性),用一个pre数组ha但是不能处理带有负边的树。
②树形DP,

网络流

最大流/最小割
Dinic
因为最大流数值上等于最小割,最小割问题也可以用Dinic求

//点从1-n 包括源点汇点 边从2开始编号 方便异或 (一次建好两条边)
const int INF = 0x3f3f3f3f;
const int N = 1e5+7;
const int MAXN = 1e5+7;
int cnt = 1, n, st, ed, d[N], cur[N], head[N];
int T, r, c;
struct Edge
{
    int to, w, nxt;
}e[MAXN<<1];
void AddEdge(int f, int t, int w)
{
    e[++cnt].nxt = head[f]; e[cnt].to = t; e[cnt].w = w; head[f] = cnt;
    e[++cnt].nxt = head[t]; e[cnt].to = f; e[cnt].w = 0; head[t] = cnt;
}
bool BFS()
{
    rep(i, 1, n) d[i] = 0;
    queue <int> q;
    q.push(st); d[st] = 1;
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].to;
            if(d[v] || !e[i].w) continue;
            q.push(v); d[v] = d[u] + 1;
        }
    }
    rep(i, 1, n) cur[i] = head[i];
    return d[ed] != 0;
}
int DFS(int u, int flow)
{
    if(u == ed) return flow;
    for(int i = cur[u]; i; i = e[i].nxt)
    {
        cur[u] = i;
        int v = e[i].to;
        if(d[v] != d[u]+1 || !e[i].w) continue;
        int det = DFS(v, min(flow, e[i].w));
        if(!det) continue;
        e[i].w -= det; e[i^1].w += det;
        return det;
    }
    return 0;
}
LL Dinic()
{
    LL maxflow = 0, det;
    while(BFS())
        while(det = DFS(st, INF))
            maxflow += det;
    return maxflow;
}
void init()
{
    cnt = 1;
    rep(i, 1, n) head[i] = 0;
}

费用流

最小(大)费用最大流

//最小费用最大流 如果求最大费用,建边cost赋值为负数,输出费用*(-1)
const int INF = 0x3f3f3f3f;
const int N = 1e4+7;
const int MAXN = 1e5+7;

int T, n, head[N], cnt = 1, dis[N], pre[N], preve[N];
struct Edge
{
    int f, to, w, c, nxt;
}e[MAXN];
//一次建两条边 cnt从1开始
void AddEdge(int f, int t, int w, int c)
{
    e[++cnt].to = t; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[f]; head[f] = cnt;
    e[++cnt].to = f, e[cnt].w = 0; e[cnt].c =-c; e[cnt].nxt = head[t]; head[t] = cnt;
}

bool SPFA(int s, int t)
{
    int inq[N];
    rep(i, 1, n)
        dis[i] = INF, inq[i] = 0, pre[i] = -1;
    dis[s] = 0;
    queue <int> q;
    q.push(s); inq[s] = 1;
    while(!q.empty())
    {
        int u = q.front(); q.pop(); inq[u] = 0;
        for(int i = head[u]; i; i = e[i].nxt)
        {
            if(e[i].w > 0)
            {
                int v = e[i].to, cost = e[i].c;
                if(dis[u] + cost < dis[v])
                {
                    dis[v] = dis[u] + cost;
                    pre[v] = u; preve[v] = i;
                    if(!inq[v])
                    {
                        q.push(v); inq[v] = 1;
                    }
                }
            }
        }
    }
    return dis[t] != INF;
}

LL MinCost(int s, int t)
{
    LL cost = 0; //maxflow = 0;
    while(SPFA(s, t))
    {
        int v = t, flow = INF;
        while(pre[v] != -1)
        {
            int u = pre[v], i = preve[v];
            flow = min(flow, e[i].w);
            v = u;
        }
        v = t;
        while(pre[v] != -1)
        {
            int u = pre[v], i = preve[v];
            e[i].w -= flow;
            e[i^1].w += flow;
            v = u;
        }
        cost += (LL)dis[t] * flow; //maxflow += flow;
    }
    return cost;
}

void init()
{
    cnt = 1;
    rep(i, 1, n)
        head[i] = pre[i] = preve[i] = dis[i] = 0;
}

最小费用流
不用管流量,上面板子当费用 >= 0时直接结束算法就是最小费用流

const int INF = 0x3f3f3f3f;
const int N = 1e4+7;
const int MAXN = 1e5+7;

int T, n, head[N], cnt = 1, dis[N], pre[N], preve[N];
struct Edge
{
    int f, to, w, c, nxt;
}e[MAXN];
//一次建两条边 cnt从1开始
void AddEdge(int f, int t, int w, int c)
{
    e[++cnt].to = t; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[f]; head[f] = cnt;
    e[++cnt].to = f, e[cnt].w = 0; e[cnt].c =-c; e[cnt].nxt = head[t]; head[t] = cnt;
}

bool SPFA(int s, int t)
{
    int inq[N];
    rep(i, 1, n)
        dis[i] = INF, inq[i] = 0, pre[i] = -1;
    dis[s] = 0;
    queue <int> q;
    q.push(s); inq[s] = 1;
    while(!q.empty())
    {
        int u = q.front(); q.pop(); inq[u] = 0;
        for(int i = head[u]; i; i = e[i].nxt)
        {
            if(e[i].w > 0)
            {
                int v = e[i].to, cost = e[i].c;
                if(dis[u] + cost < dis[v])
                {
                    dis[v] = dis[u] + cost;
                    pre[v] = u; preve[v] = i;
                    if(!inq[v])
                    {
                        q.push(v); inq[v] = 1;
                    }
                }
            }
        }
    }
    return dis[t] != INF;
}

LL MinCost(int s, int t)
{
    LL cost = 0; //maxflow = 0;
    while(SPFA(s, t))
    {
    	//变化在这
    	if(dis[t] >= 0)
    		break;
        int v = t, flow = INF;
        while(pre[v] != -1)
        {
            int u = pre[v], i = preve[v];
            flow = min(flow, e[i].w);
            v = u;
        }
        v = t;
        while(pre[v] != -1)
        {
            int u = pre[v], i = preve[v];
            e[i].w -= flow;
            e[i^1].w += flow;
            v = u;
        }
        cost += (LL)dis[t] * flow; //maxflow += flow;
    }
    return cost;
}

void init()
{
    cnt = 1;
    rep(i, 1, n)
        head[i] = pre[i] = preve[i] = dis[i] = 0;
}

二分图匹配

Hungarian最佳匹配
//只匹配二分图的左边,建从左到右的单向边即可,match[i]是右边的i号节点目前匹配的左边的哪个节点
bool dfs(int x, int tag)
{
    if(vis[x] == tag) return 0;
    vis[x] = tag;
	for(int it : G[x])
    {
        if(!match[it] || dfs(match[it], tag))
        {
            match[it] = x;
            return true;
        }
    }
	return false;
}
void Hungarian()
{
    ans = 0;
    memset(vis, 0, sizeof(vis));
	for(int i = 1; i <= n; i++)
	    if(dfs(i, i)) ans++;
	cout << ans << endl;
}
int main()
{
    cin >> n >> m >> t;
    int u, v;
    rep(i, 1, t)
    {
        cin >> u >> v;
        G[u].pb(v);
    }
    Hungarian();
    return 0;
}
KM完美匹配

2-SAT问题

暴力染色
复杂度 n*m 损色于缩点,但是一般不卡,且如果要求字典序最小,只能暴力
自己看着建边 u->v 如果选u则一定选v
i<<1 号点表示不选i i<<1|1号点表示选i

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize (Ofast)
#define fastio ios_base::sync_with_stdio(0); cin.tie(NULL);
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define LL long long
#define mp make_pair
#define pb push_back
#define fr first
#define se second
#define endl "\n"
#define debug1 cout << "???" << endl;
#define debug2(x) cout << #x << ": " << x << endl;
const int INF = 0x3f3f3f3f;
const int N = 2e6+7;
int n, m, mark[N];
stack <int> st;
vector <int> G[N];
int DFS(int x)
{
    if(mark[x^1])
        return 0;
    if(mark[x])
        return 1;
    mark[x] = 1;
    st.push(x);
    for(auto i : G[x])
        if(!DFS(i))
            return 0;
    return 1;
}
int Solve()
{

    rep(i, 1, n)
    {
        if(!mark[i<<1] && !mark[i<<1|1])
        {
            while(!st.empty())
                st.pop();
            if(!DFS(i<<1))
            {
                while(!st.empty())
                {
                    mark[st.top()] = 0;
                    st.pop();
                }
                if(!DFS(i<<1|1))
                    return 0;
            }
        }
    }
    return 1;
}
int main()
{
    fastio
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    while(cin >> n >> m)
    {
    	//注意初始化到2*n+1
        rep(i, 1, ((n+1)<<1))
        {
            G[i].clear();
            mark[i] = 0;
        }
        int t1, t2, t3, t4, u, v;
        rep(i, 1, m)
        {
            cin >> t1 >> t2 >> t3 >> t4;
            u = t1<<1|t2;
            v = t3<<1|t4;
            G[u^1].pb(v);
            G[v^1].pb(u);
        }
        if(!Solve())
        {
            cout << "IMPOSSIBLE\n" << endl;
            continue;
        }
        cout << "POSSIBLE" << endl;
        rep(i, 1, n)
        {
            if(mark[i<<1])
                cout << "0 ";
            else
                cout << "1 ";
        }
    }
    return 0;
}

Tarjan缩点
建图就是如果选这个点,那么一点会选连着这个点的其他点(利用逆否命题和原命题等价)

void Tarjan(int u) //tarjan缩点 缩成一块块强连通分量,因为到达一个就会到达所有的点,所以要么都选,要么都不选
{
	dfn[u] = low[u] = ++dep;
	stk[++tp] = u; ins[u] = 1;
	for(int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].t;
		if(!dfn[v]) 
		{
			Tarjan(v);
			low[u] = min(low[v], low[u]);
		}
		else if(ins[v])
			low[u] = min(low[v], low[u]);
	}
	if(dfn[u] == low[u])
	{
		color[u] = ++tot;
		ins[u] = 0;
		while(stk[tp] != u)
		{
			color[stk[tp]] = tot; //注意一下缩点的顺序 是在树上dfs下去 一块一块的 染色序号就是拓扑倒序
			ins[stk[tp--]] = 0;
		}
		tp--;
	}
}
void Solve()
{
	for(int i = 2; i <= (n<<1) + 1; i++) //给全部点染色
		if(!dfn[i]) Tarjan(i);
	for(int i = 2; i <= (n<<1) ; i += 2) //如果一个点的两种情况都在一个连通分量里 说明两个必须同时成立 那不可能
		if(color[i] == color[i^1])
		{
			printf("IMPOSSIBLE\n");
			return;
		}
	printf("POSSIBLE\n");
	for(int i = 2; i <= (n<<1); i += 2) //
		printf("%d ", (color[i] > color[i^1])); 
}

字符串

哈希

康托展开

LL cantor(string s) //康托展开 初始序列为0 如果初始看作1要手动+1 逆展开手动-1
{
    LL res = 0;
    int len = s.size();
    rep(i, 0, len-1)
    {
        int cnt = 0;
        rep(j, i+1, len-1) if(s[i] > s[j]) cnt++;
        res += fac[len-i-1] * cnt;
    }
    return res+1;
}
string rev_cantor(string s, LL k) //康托逆展开,返回字符串s第k个排列(k从1开始)
{
    vector <char> vec;
    string res = "";
    int len = s.size(); k--;
    rep(i, 0, len-1)
        vec.pb(s[i]);
    rep(i, 1, len)
    {
        int t = k / fac[len-i];
        k %= fac[len-i];
        res += vec[t];
        vec.erase(vec.begin() + t);
    }
    return res;
}

数字Hash

我的建议是unordered_map
用一个vector存冲突的哈希 可以用空间换时间 或者时间换空间

const int N = 1e5+7;
vector <string> Hs[N];
cin >> s;
int h = Hash(s) % N; 
//哈希值是新的
if(Hs[h].empty())
{
	ans++;
	Hs[h].push_back(s);
}
else //哈希值存在 跑一遍同样的判断是否重复
{
	int sz = Hs[h].size(), flag = 1;
	string tmp;
	for(int i = 0; i < sz; i++)
		if(!Hs[h][i].compare(s))
		{
			flag = 0;
			break;
		}
	if(flag)
	{
		Hs[h].push_back(s);
		ans++; 
	}
}

KMP

//s1标准串 s2模式串
void GetNext()
{
	nxt[0] = -1;
	int i = -1, j = 0; // -1很妙啊 因为如果标准串第一个就和给定串不一样,那就让 i变为0 j+1 上个nxt赋值i = -1 那么直接都++即可 
	while(j < len2)
	{
		if(i == -1 || s2[i] == s2[j])
		{
			i++;j++;
		    nxt[j] = i;
		}
		else
			i = nxt[i]; //有点递归的思想,因为处理到i了 i之前的nxt都处理了 如果当前位不一样,那之前的一样,也可以利用前后缀相同跳回去继续匹配,不用回到开头了 也有字符串匹配的思想 属于是在标准串里找前缀串了
	}
 } 
int KMP()
{
	GetNext();
	int i = 0, j = 0;
	while(j < len1 && i < len2)
	{
		if(i == -1 || s1[j] == s2[i])
		{
			if(i == len2-1)
			{
				ans[++cnt] = j - len2 + 1; //记录在第一个字符在原串中的位置 
				i = nxt[i]; //如果要求不重复 改为 i = -1
				continue; 
			} 
			i++;j++;
		}
		else i = nxt[i];
	}
	return cnt;
}

拓展KMP

字典树

//字典树 可以插入单词,统计单词出现次数,也可以统计前缀,前序遍历输出
//每插入一个字母就ed[pos]++;即可做到统计某个字符串作为前缀出现的次数
int trie[N][26];
int Trie(string s)
{
	len = s.length();
	int pos = 0;
	for(int i = 0; i < len; i++)
	{
		int t = s[i] - 'a';
		if(!trie[pos][t])
			trie[pos][t] = ++num;
		pos = trie[pos][t]; //ed[pos]++; 统计前缀 
	}
	ed[pos]++;
	return ed[pos];
}

Manacher

O(n)找回文串

//预处理  
scanf("%s", tmp+1);
int len = strlen(tmp+1); 
for(int i = 1; i <= len; i++)
{
	s[i<<1] = tmp[i];
	s[(i<<1)|1] = '#';
}
s[0] = '@';s[1] = '#';
len = (len<<1)|1; 
//p[i]为新得到的字符串第i哥位置为对称中心的回文串长度(长度不算新增字符)
for(int i = 1; i <= len; i++)
{
	if(i < rmax) //第一种情况,如果i可以对称过去到(pos*2-i),就更新一下p[i] 
		p[i] = min(p[(pos<<1) - i], rmax - i);
	while(s[i+p[i]+1] == s[i-p[i]-1]) //暴力找完对称点 
		p[i]++;
	if(i + p[i] > rmax) //维护 rmax 和 pos 
	{
		rmax = i + p[i];
		pos = i;
	}
}

AC自动机

//利用字符串序号插入计数版 ans[i]为第i个字符串出现的次数
int n, cnt, ans[N];
string s1[N], s;
//字典树
struct Trie
{
	int child[26], fail, ed; //fail指向的点代表的字符串是当前点的最长后缀 (fail指向的点如果不是root 那这个点的字母和当前点一样)
}AC[N];
//插入单词 如果没有num即序号 就需要用一个map<string, int>来记录
void Build(string s, int num)
{
	int len = s.length();
	int pos = 0;
	for(int i = 0; i < len; i++)
	{
		int t = s[i] - 'a';
		if(!AC[pos].child[t]) 
			AC[pos].child[t] = ++cnt;
		pos = AC[pos].child[t];
	}
		AC[pos].ed = num;
}
//构建fail指针
void GetFail()
{
	queue <int> q;
	int v;
	for(int i = 0; i < 26; i++)
	{
		v = AC[0].child[i];
		if(v)
		{AC[v].fail = 0; q.push(v);}
	}

	while(!q.empty())
	{
		int u = q.front();q.pop();
		for(int i = 0; i < 26; i++)
		{
			int v = AC[u].child[i];
			if(AC[u].child[i])
			{
				AC[v].fail = AC[AC[u].fail].child[i];
				q.push(v);
			}
			else
				AC[u].child[i] = AC[AC[u].fail].child[i];
		}
	}
}
//跑一遍文本串即可
void ACQuery(string s)
{
	int len = s.length(), pos = 0;
	for(int i = 0; i < len; i++)
	{
		int t = s[i] - 'a';
		int v =  AC[pos].child[t];
		//因为是统计出现次数 所以会重复计数 只要不到根 就一直跳fail 因为走到这个点就等价于走了 root到fail节点
		while(v)
		{
			ans[AC[v].ed]++;
			v = AC[v].fail;
		}
		pos = AC[pos].child[t];
	}
} 

void Clr()
{
	for(int i = 0; i <= cnt; i++)
	{
		AC[i].ed = 0;
		AC[i].fail = 0;
		memset(AC[i].child, 0, sizeof(AC[i].child));
	}
	memset(ans, 0, sizeof(ans));
	cnt = 0;
}

前缀自动机
看文本串最长出现的前缀(因为读了个假题搜了一下)

#include <bits/stdc++.h>
const int INF = 0x3f3f3f3f;
const int N = 1e6+7;
using namespace std;
int n, w, hd, tl, len, q[N], b[N], d[N], v[N], nx[N], fa[N], to[N][30];
char s[N], ss[N];
string s1[N];

int check(char g)
{
    return (g - 'a');
}
int insert(char* s)
{
	int now = 0, n = strlen(s+1);
	for (int i = 1; i <= n; ++i)
	{
		int y = check(s[i]);
		if (!to[now][y]) to[now][y] = ++w;
		fa[to[now][y]] = now;
		q[to[now][y]] = q[now] + 1;
		now = to[now][y];
	}
	return now;
}
void bfs()
{
	for (int i = 0; i < 26; ++i)
		if (to[0][i]) d[++tl] = to[0][i];
	while(hd < tl)
	{
		int h = d[++hd];
		for (int i = 0; i < 26; ++i)
			if (!to[h][i]) to[h][i] = to[nx[h]][i];
			else nx[to[h][i]] = to[nx[h]][i], d[++tl] = to[h][i];
	}
	return;
}
void ask(char* s)
{
	int now = 0;
	for (int i = 1; i <= len; ++i)
	{
		int y = check(s[i]);
		now = to[now][y];
		b[now] = 1;
	}
	return;
}
int find(int x)//倒着跑匹配串
{
	while(x && !b[x]) x = fa[x];
	return q[x];
}
int main()
{
//    freopen("in.txt", "r", stdin);
	scanf("%s", ss+1);
	len = strlen(ss+1);
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
	{
	    cin >> s1[i];
	    for(int j = 0; j < s1[i].size(); j++)
            s[j+1] = s1[i][j];
		v[i] = insert(s);
	}
	bfs();
	ask(ss);
	for (int i = tl; i > 0; --i)
		b[nx[d[i]]] |= b[d[i]];//传递贡献
	for (int i = 1; i <= n; ++i)
    {
		int sz = find(v[i]);
		if(!sz)
        {
            printf("IMPOSSIBLE\n");
            continue;
        }
        rep(j, 0, sz-1)
            cout << s1[i][j];
        cout <<endl;
    }
	return 0;
}

数据结构

并查集

//初始化 f[i] = i; h[i] = 0; 
//并查集 查找 
int find_set(int x)
{
	if(x != f[x]) f[x] = find_set(f[x]);
	return f[x];
} 
//合并
int union_set(int x, int y)
{
	x = find_set(x);
	y = find_set(y);
	if(h[x] == h[y])
	{
		h[x]++;
		f[y] = x;
	}
	else //把矮树合并到高的树上 
	{
		if(h[x] < h[y]) f[x] = y;
		else f[y] = x;
	}
 } 

ST表

/*分成2的幂的区间,nlogn预处理,st[i][j]为以i开始之后2^j个数的最大/最小值
  询问只要知道区间包含最大的2的幂次数maxk, min(st[l][maxk], st[r-k[maxk]-1][maxk]) 即         是,注意求maxk别忘了+1 求右边那段别忘了-1*/
int k[24], st[N][24], n, m, a[N]; //2^23 8e6
//ST()预处理
void ST()
{
	k[0] = 1;
	rep(i, 1, 23) k[i] = k[i-1]<<1;
    rep(i, 1, n) st[i][0] = a[i];
    int maxk = log2(n);
    rep(j, 1, maxk)
        rep(i, 1, n)
        {
        	if(i + t[j] - 1 > n) break;
            st[i][j] = min(st[i][j-1], st[i+k[j-1]][j-1]);
        }
}
int Query(int l, int r)
{
    int maxk = log2(r - l + 1);
    return min(st[l][maxk], st[r-k[maxk]+1][maxk]);
}

分块

求区间和(异或,最大值…)

//tag标签不用下放 start end 属于哪个块 整块的和 lazytag 整块长度(主要是最后一个不是sq长)
int sq, n, m, a[N], st[SQ], ed[SQ], bel[N], sum[SQ], tag[SQ], len[SQ];
void Build()
{
    sq = sqrt(n);
    rep(i, 1, sq)
    {
        st[i] = sq * (i-1) + 1;
        ed[i] = sq * i;
        len[i] = sq;
    }
    len[sq] += n - ed[sq];
    ed[sq] = n;
    rep(i, 1, sq)
        rep(j, st[i], ed[i])
            bel[j] = i, sum[i] += a[j];
}
//如果区间在一个块内 直接暴力维护
void Add(int l, int r, int k)
{
    if(bel[l] == bel[r])
    {
        rep(i, l, r)
        {
            a[i] += k;
            sum[bel[i]] += k;
        }
        return;
    }
    //不然把两边散块先暴力维护了,再对中间整块打tag
    rep(i, l, ed[bel[l]])
    {
        a[i] += k;
        sum[bel[i]] += k;
    }
    rep(i, st[bel[r]], r)
    {
        a[i] += k;
        sum[bel[i]] += k;
    }
    rep(i, bel[l]+1, bel[r]-1)
        tag[i] += k;
}
//询问也是一个道理
long long Query(int l, int r)
{
    long long ans = 0;
    if(bel[l] == bel[r])
    {
        rep(i, l, r)
            ans += a[i] + tag[bel[i]];
        return ans;
    }
    rep(i, l, ed[bel[l]]) ans += a[i] + tag[bel[i]];
    rep(i, st[bel[r]], r) ans += a[i] + tag[bel[i]];
    rep(i, bel[l]+1, bel[r]-1) ans += sum[i] + tag[i] * len[i];
    return ans;
}

动态维护区间 并询问区间大于x的数的个数
分块 散块暴力 整块排序 用二分查找求数
散块暴力维护 重新排 整块不影响有序性

const int N = 1e6+7;
const int SQ = 1e3+7;
int sq, n, q, bel[N], len[SQ], st[SQ], ed[SQ];
LL a[N], mark[SQ];
vector <LL> b[SQ];
void Build();
void Add(int l, int r, LL k);
void Update(int x);
int Query(int l, int r, LL k);
int main()
{
    cin >> n >> q;
    rep(i, 1, n) cin >> a[i];
    Build();
    char op; int l, r; LL k;
    while(q--)
    {
        cin >> op >> l >> r >> k;
        if(op == 'A') cout << Query(l, r, k) << endl;
        else Add(l, r, k);
    }
    return 0;
}
void Build()
{
    sq = sqrt(n);
    rep(i, 1, sq)
    {
        st[i] = sq * (i-1) + 1;
        ed[i] = sq * i;
        len[i] = sq;
    }
    len[sq] += n - ed[sq];
    ed[sq] = n;
    rep(i, 1, sq)
    {
        b[i].resize(ed[i] - st[i] + 1);
        rep(j, st[i], ed[i])
        {
            bel[j] = i;
            b[i][j-st[i]] = a[j];
        }
        sort(b[i].begin(), b[i].end());
    }
}
void Add(int l, int r, LL k)
{
    if(bel[l] == bel[r])
    {
        rep(i, l, r) a[i] += k;
        Update(bel[l]);
        return;
    }
    rep(i, l, ed[bel[l]]) a[i] += k;
    Update(bel[l]);
    rep(i, st[bel[r]], r) a[i] += k;
    Update(bel[r]);
    rep(i, bel[l]+1, bel[r]-1)  mark[i] += k;
}

int Query(int l, int r, LL k)
{
    LL tmp;
    int cnt = 0;
    if(bel[l] == bel[r])
    {
        tmp = k - mark[bel[l]];
        rep(i, l, r) if(a[i] >= tmp) cnt++;
        return cnt;
    }
    tmp = k - mark[bel[l]];
    rep(i, l, ed[bel[l]]) if(a[i] >= tmp) cnt++;
    tmp = k - mark[bel[r]];
    rep(i, st[bel[r]], r) if(a[i] >= tmp) cnt++;
    rep(i, bel[l]+1, bel[r]-1)
    {
        tmp = k - mark[i];
        cnt += b[i].end() - lower_bound(b[i].begin(), b[i].end(), tmp);
    }
    return cnt;
}
void Update(int x)
{
    rep(i, 0, len[x]-1) b[x][i] = a[st[x]+i];
    sort(b[x].begin(), b[x].end());
}

区间众数

const int N = 6e4+7;
const int MAXN = 260;
// cnt[i][j] 第i块j的数量 g[i][j] 前i块j的数量
int T, n, m, st[MAXN], b[N], ed[MAXN], len[MAXN], maxn[MAXN][MAXN], idx, bel[N];
unsigned short g[MAXN][N], cnt[MAXN][N];
pair <int, int> a[N];

bool cmp(pair<int, int> a1, pair<int, int> a2)
{
    return a1.fr < a2.fr;
}

void Build()
{
    int sq = sqrt(n);
    rep(i, 1, sq)
        st[i] = (i-1) * sq + 1, ed[i] = i * sq, len[i] = sq;
    len[sq] += n - ed[sq];
    ed[sq] = n;

    rep(i, 1, sq)
    {
        rep(j, st[i], ed[i])
            cnt[i][b[j]]++, bel[j] = i;
        rep(j, 1, idx)
            g[i][j] = g[i-1][j] + cnt[i][j];
    }

    // i -> j
    rep(i, 1, sq)
    {
        rep(j, i, sq)
        {
            maxn[i][j] = maxn[i][j-1];
            rep(k, st[j], ed[j])
                maxn[i][j] = max((int)maxn[i][j], (int)cnt[j][b[k]] + (int)g[j-1][b[k]] - (int)g[i-1][b[k]]);
        }
    }
}

int Query(int l, int r)
{
    vector <int> vec;
    rep(i, l, ed[bel[l]])
        vec.pb(b[i]);
    rep(i, st[bel[r]], r)
        vec.pb(b[i]);
    sort(vec.begin(), vec.end());
    int fst = 0, sz = vec.size();
    int L = bel[l]+1, R = bel[r]-1;
    int ans = maxn[L][R];
    rep(i, 1, sz-1)
    {
        if(vec[i] != vec[i-1])
        {
            ans = max(ans, (int)(g[R][vec[i-1]] - g[L-1][vec[i-1]] + i-fst));
            fst = i;
        }
    }
    ans = max(ans, (int)(g[R][vec[sz-1]] - g[L-1][vec[sz-1]] + sz-fst));
    return ans;
}

int main()
{
    cin >> T;
    while(T--)
    {
        memset(g, 0, sizeof(g));
        memset(cnt, 0, sizeof(cnt));
        memset(maxn, 0, sizeof(maxn));
        cin >> n >> m;
        rep(i, 1, n)
            cin >> a[i].fr, a[i].se = i;
        sort(a+1, a+n+1, cmp);
        idx = 0;
        rep(i, 1, n)
        {
            if(a[i].fr == a[i-1].fr)
                b[a[i].se] = idx;
            else
                b[a[i].se] = ++idx;
        }
        Build();
        int l, r, ans = 0;
        rep(i, 1, m)
        {
            cin >> l >> r;
            l ^= ans, r ^= ans;
            ans = Query(l, r);
            cout << ans << endl;
        }
    }
    return 0;
}

树状数组

/* 单点修改,区间查询
   区间修改,单点查询 (维护一个差分数列,单点查询就是求差分数列和,区间修改就是在l处加上x,在r+1处减去x)
   求和 求积 求异或和(满足交换律的)
   区间修改,求区间最大最小值以及其位置*/
int lowbit(int x)
{return x & -x;}
void Add(int pos, int x)
{
	while(pos <= n)
	{
		bit[pos] += x;
		pos += lowbit(pos);
	}
}
LL Query(int pos)
{
	LL res = 0;
	while(pos)
	{
		res += bit[pos];
		pos -= lowbit(pos);	
	}
	return res;
 }
//求最小值最大值 复杂度 (logn)^2 比线段树慢 st表最快
void Updata(int x)
{
	while (x <= n)
	{
		bit[x] = a[x];
		int lb = lowbit(x);
		for(int i = 1; i < lb; i<<=1) bit[x] = max(bit[x], bit[x-i]);
		x += lowbit(x);
	}
}
int Query(int l, int r)
{
	int ans = 0;
	while (r >= l)
	{
		ans = max(a[r], ans);
		r--;
		for (; r-lowbit(r) >= l; r -= lowbit(r)) ans = max(bit[r], ans);
	}
	return ans;
}

线段树

详见线段树总结专题。
普通线段树

#include <bits/stdc++.h>
#define Ls pos<<1
#define Rs pos<<1|1
#define LL long long
using namespace std;
const int N = 1e5+7;
struct SegTree
{
	LL val, mul, add; //val为区间和 mul为乘法lazy tag add 
}t[N<<2];
int a[N], n, m, p;
//!注意lazy_tag 和 当前节点的 val 是同时更新的 更新这个点lazy tag的同时保证这个点的的val一定是正确的值了
void Build(int pos, int l, int r)
{
	t[pos].mul = 1; 
	t[pos].add = 0;
	if(l == r){t[pos].val = a[l]; return;}
	int mid = (l+r)>>1;
	Build(Ls, l, mid);
	Build(Rs, mid+1, r);
	t[pos].val = (t[Ls].val + t[Rs].val) % p; 
}
//因为有乘法,同时维护了两个lazy tag 就需要注意一下顺序了 保证每次都是先乘再加
void PushDown(int pos, int len)
{
	t[Ls].val = (t[Ls].val * t[pos].mul + t[pos].add * (len - (len>>1))) % p;
	t[Rs].val = (t[Rs].val * t[pos].mul + t[pos].add * (len>>1)) % p;
	t[Ls].mul = (t[pos].mul * t[Ls].mul) % p;
	t[Rs].mul = (t[pos].mul * t[Rs].mul) % p;
	t[Ls].add = (t[pos].mul * t[Ls].add + t[pos].add) % p;
	t[Rs].add = (t[pos].mul * t[Rs].add + t[pos].add) % p;
	t[pos].mul = 1; //最后记得清空 父亲的lazy tag
	t[pos].add = 0;

}
void Add(int pos, int l, int r, int L, int R, int x)
{
	if(L <= l && r <= R) //如果全覆盖 直接更新值 同时更新lazy tag
	{
		t[pos].val = (t[pos].val + x*(r-l+1)) % p;
		t[pos].add = (t[pos].add + x) % p;
		return;
	}
	//只要往下走 就得PushDown把lazy tag放到下一层 保证用到Lson 和 Rson 的时候他们的值就是真实值
	PushDown(pos, r-l+1);
	int mid = (l+r)>>1;
	if(L <= mid)
		Add(Ls, l, mid, L, R, x);
	if(R > mid)
		Add(Rs, mid+1, r, L, R, x);
	t[pos].val = (t[Ls].val + t[Rs].val) % p;
}
void Mul(int pos, int l, int r, int L, int R, int x)
{
	if(L <= l && r <= R)
	{
		t[pos].val = (t[pos].val * x) % p;
		t[pos].mul = (t[pos].mul * x) % p;
		t[pos].add = (t[pos].add * x) % p;
		return;
	}
	PushDown(pos, r-l+1);
	int mid = (l+r)>>1;
	if(L <= mid)
		Mul(Ls, l, mid, L, R, x);
	if(R > mid)
		Mul(Rs, mid+1, r, L, R, x);
	t[pos].val = (t[Ls].val + t[Rs].val) % p;
}
LL Query(int pos, int l, int r, int L, int R)
{
	LL res = 0;
	if(L <= l && r <= R)
	{return t[pos].val % p;}
	PushDown(pos, r-l+1); //因为加,乘取的区间和问的不一定一样,所有的lazy tag能不放的都没放下来 但是询问必须是真实值 所以用到哪 放到哪
	int mid = (l+r)>>1;
	if(L <= mid)
		res = (res + Query(Ls, l, mid, L, R)) % p;
	if(R > mid)
		res = (res + Query(Rs, mid+1, r, L, R)) % p;
	return res;
}
int main()
{
	scanf("%d %d %d", &n, &m, &p);
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	Build(1, 1, n); 
	for(int way, t1, t2, t3, i = 1; i <= m; i++) 
	{
		scanf("%d", &way);
		if(way == 1)
		{
			scanf("%d %d %d", &t1, &t2, &t3);
			Mul(1, 1, n, t1, t2, t3);
		}
		else if(way == 2)
		{
			scanf("%d %d %d", &t1, &t2, &t3);
			Add(1, 1, n, t1, t2, t3);
		}
		else
		{
			scanf("%d %d", &t1, &t2);
			printf("%lld\n", Query(1, 1, n, t1, t2));
		}
	}
	return 0;
} 

可持久化数组线段树
可以在任意一次操作的版本单点修改区间查询
因为每次修改多加了一条链,但仍然是个完整的树

//300mb n= 1e6
const int INF = 0x3f3f3f3f;
const int N = 3e7+7;
struct kkk
{
    int l, r, val;
}t[N];
int n, m, a[N], rot[N], top;
//复制一个节点
int Clone(int rt)
{
    t[++top] = t[rt];
    return top;
}
void PushUp(int rt)
{t[rt].val = t[t[rt].l].val + t[t[rt].r].val;}

int Build(int l, int r)
{
    int rt = ++top;
    if(l == r){t[rt].val = a[l]; return rt;}
    int mid = l+r>>1;
    t[rt].l = Build(t[rt].l, l, mid);
    t[rt].r = Build(t[rt].r,mid+1,r);
    PushUp(rt);
    return rt;
}
//单点修改 调用时返回的是新的链的根节点 存为新版本的根节点
int Updata(int rt, int l, int r, int x, int val)
{
    rt = Clone(rt);
    if(l == r){t[rt].val = val; return rt;}
    int mid = l+r>>1;
    if(x <= mid)
        t[rt].l = Updata(t[rt].l, l, mid, x, val);
    else
        t[rt].r = Updata(t[rt].r,mid+1,r, x, val);
    PushUp(rt);
    return rt;
}
//单点查询 和正常线段树一样
int Query(int rt, int l, int r, int x)
{
    if(l == r) return t[rt].val;
    int mid = l+r>>1;
    if(x <= mid)
        return Query(t[rt].l, l, mid, x);
    else
        return Query(t[rt].r,mid+1,r, x);
}
//区间查询
LL Query1(int rt, int l, int r, int L, int R)
{
    LL res = 0;
    if(L <= l && r <= R) return t[rt].val;
    int mid = l + r >> 1;
    if(L <= mid)
        res += Query1(t[rt].l, l, mid, L, R);
    if(R > mid)
        res += Query1(t[rt].r,mid+1,r, L, R);
    return res;
}

int main()
{
    fastio

//    freopen("in.txt", "r", stdin);
    cin >> n >> m;
    rep(i, 1, n)
        cin >> a[i];
    //初始版本为0,根节点为1,一个操作对应一个版本
    rot[0] = Build(1, n);
    int ver, opt, x, y, val;
    rep(i, 1, m)
    {
        cin >> ver >> opt;
        if(opt == 1)
        {
            cin >> x >> val;
            rot[i] = Updata(rot[ver], 1, n, x, val);
        }
        else
        {
            cin >> x >> y;
            cout << Query1(rot[ver], 1, n, x, y) << endl;
            rot[i] = rot[ver];
        }
    }
    return 0;
}

主席树
可持久化线段树升级版
求区间第k小,区间mex,存每个点状态用。
要求每个点只与之前某个状态有一个不同,更新出一个新状态建一条链。
一般维护线段树的区间为权值区间,叶子节点代表一个权值,不再是位置。

const int INF = 0x3f3f3f3f;
const int N = 1e5+7;
const int LOG = 21;

int T, n, m, top, a[N], b[N], rot[N];

struct kkk
{
    int l, r, val;
}t[N*LOG];

int Clone(int rt)
{
    t[++top] = t[rt];
    return top;
}

int Build(int l, int r)
{
    int rt = ++top;
    if(l == r) return rt;
    int mid = l+r>>1;
    t[rt].l = Build(l, mid);
    t[rt].r = Build(mid+1,r);
    return rt;
}

int Updata(int rt, int l, int r, int x, int val)
{
    rt = Clone(rt);
    t[rt].val += val;
    if(l == r) return rt;
    int mid = l+r>>1;
    if(x <= mid)
        t[rt].l = Updata(t[rt].l, l, mid, x, val);
    else
        t[rt].r = Updata(t[rt].r,mid+1,r, x, val);
    return rt;
}

int Solve(int u, int v, int l, int r, int k)
{
    if(l == r) return l;
    int det = t[t[v].l].val - t[t[u].l].val, mid = l+r>>1;
    if(det >= k)
        return Solve(t[u].l, t[v].l, l, mid, k);
    else
        return Solve(t[u].r, t[v].r,mid+1,r, k-det);
}

void init()
{
    top = 0;
    int log = log2(n)+1;
    rep(i, 1, log*n)
        t[i].val = 0;
}

int main()
{
    fastio
//    freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--)
    {
        cin >> n >> m;
        init();
        rep(i, 1, n)
            cin >> a[i], b[i] = a[i];
        sort(b+1, b+n+1);
        int sz = unique(b+1, b+n+1) - b - 1;
        rot[0] = Build(1, sz);
        rep(i, 1, n)
        {
            a[i] = lower_bound(b+1, b+sz+1, a[i]) - b;
            rot[i] = Updata(rot[i-1], 1, sz, a[i], 1);
        }
        int l, r, k;
        rep(i, 1, m)
        {
            cin >> l >> r >> k;
            cout << b[Solve(rot[l-1], rot[r], 1, sz, k)] << endl;
        }
    }
    return 0;
}

计算几何

杂项

离散化

rep(i, 1, n) cin >> a[i], b[i] = a[i];
sort(b+1, b+1+n);
m = unique(b+1, b+1+n)-b-1;
rep(i, 1, n)
	a[i] = lower_bound(b+1, b+1+m, a[i]) - b;

高精度

//可以用long long 或者string初始化
//注意减法必须被减数大于减数 用>=判断
struct BI {
    typedef unsigned long long LL;
    static const int BASE = 100000000;
    static const int WIDTH = 8;
    vector<int> s;
    BI& clean(){while(!s.back()&&s.size()>1)s.pop_back(); return *this;}
    BI(LL num = 0) {*this = num;}
    BI(string s) {*this = s;}
    BI& operator = (long long num) {
        s.clear();
        do {
            s.push_back(num % BASE);
            num /= BASE;
        } while (num > 0);
        return *this;
    }
    BI& operator = (const string& str) {
        s.clear();
        int x, len = (str.length() - 1) / WIDTH + 1;
        for (int i = 0; i < len; i++) {
            int end = str.length() - i*WIDTH;
            int start = max(0, end - WIDTH);
            sscanf(str.substr(start,end-start).c_str(), "%d", &x);
            s.push_back(x);
        }
        return (*this).clean();
    }

    BI operator + (const BI& b) const {
        BI c; c.s.clear();
        for (int i = 0, g = 0; ; i++) {
            if (g == 0 && i >= s.size() && i >= b.s.size()) break;
            int x = g;
            if (i < s.size()) x += s[i];
            if (i < b.s.size()) x += b.s[i];
            c.s.push_back(x % BASE);
            g = x / BASE;
        }
        return c;
    }
    BI operator - (const BI& b) const {
        assert(b <= *this); // 减数不能大于被减数
        BI c; c.s.clear();
        for (int i = 0, g = 0; ; i++) {
            if (g == 0 && i >= s.size() && i >= b.s.size()) break;
            int x = s[i] + g;
            if (i < b.s.size()) x -= b.s[i];
            if (x < 0) {g = -1; x += BASE;} else g = 0;
            c.s.push_back(x);
        }
        return c.clean();
    }
    BI operator * (const BI& b) const {
        int i, j; LL g;
        vector<LL> v(s.size()+b.s.size(), 0);
        BI c; c.s.clear();
        for(i=0;i<s.size();i++) for(j=0;j<b.s.size();j++) v[i+j]+=LL(s[i])*b.s[j];
        for (i = 0, g = 0; ; i++) {
            if (g ==0 && i >= v.size()) break;
            LL x = v[i] + g;
            c.s.push_back(x % BASE);
            g = x / BASE;
        }
        return c.clean();
    }
    BI operator / (const BI& b) const {
        assert(b > 0);  // 除数必须大于0
        BI c = *this;       // 商:主要是让c.s和(*this).s的vector一样大
        BI m;               // 余数:初始化为0
        for (int i = s.size()-1; i >= 0; i--) {
            m = m*BASE + s[i];
            c.s[i] = bsearch(b, m);
			m -= b*c.s[i];
        }
        return c.clean();
    }
    BI operator % (const BI& b) const { //方法与除法相同
        BI c = *this;
        BI m;
        for (int i = s.size()-1; i >= 0; i--) {
            m = m*BASE + s[i];
            c.s[i] = bsearch(b, m);
            m -= b*c.s[i];
        }
        return m;
    }
    // 二分法找出满足bx<=m的最大的x
    int bsearch(const BI& b, const BI& m) const{
        int L = 0, R = BASE-1, x;
        while (1) {
            x = (L+R)>>1;
            if (b*x<=m) {if (b*(x+1)>m) return x; else L = x;}
            else R = x;
        }
    }
    BI& operator += (const BI& b) {*this = *this + b; return *this;}
    BI& operator -= (const BI& b) {*this = *this - b; return *this;}
    BI& operator *= (const BI& b) {*this = *this * b; return *this;}
    BI& operator /= (const BI& b) {*this = *this / b; return *this;}
    BI& operator %= (const BI& b) {*this = *this % b; return *this;}

    bool operator < (const BI& b) const {
        if (s.size() != b.s.size()) return s.size() < b.s.size();
        for (int i = s.size()-1; i >= 0; i--)
            if (s[i] != b.s[i]) return s[i] < b.s[i];
        return false;
    }
    bool operator >(const BI& b) const{return b < *this;}
    bool operator<=(const BI& b) const{return !(b < *this);}
    bool operator>=(const BI& b) const{return !(*this < b);}
    bool operator!=(const BI& b) const{return b < *this || *this < b;}
    bool operator==(const BI& b) const{return !(b < *this) && !(b > *this);}
};

ostream& operator << (ostream& out, const BI& x) {
    out << x.s.back();
    for (int i = x.s.size()-2; i >= 0; i--) {
        char buf[20];
        sprintf(buf, "%08d", x.s[i]);
        for (int j = 0; j < strlen(buf); j++) out << buf[j];
    }
    return out;
}

istream& operator >> (istream& in, BI& x) {
    string s;
    if (!(in >> s)) return in;
    x = s;
    return in;
}

排序

//归并排序(每次拍L到R-1) 可以用来nlogn求逆序对 
int merge_sort(int *A, int L, int R, int * T)
{
	if(R-L > 1)
	{
		int m = L + (R-L)/2;
		merge_sort(A, L, m, T);
		merge_sort(A, m, R, T);
		int p = L, q = m, i = L;
		while(p < m || q < R)
		{
			if(q >= R || (p < m && A[p] < A[q])) T[i++] = A[p++];
			else T[i++] = A[q++]; //  cnt+= m - p; 加上就是求逆序对了 因为左边序列有m-p个比右边当前要排的数大的数 
		}
		for(i = L; i < R; i++) A[i] = T[i];
	}
}

二分答案

尽量向左

while(l < r)
{
    mid = l + r >> 1;
    if(Check(mid))
        r = mid;
    else
        l = mid + 1;
}
return l

求满足要求的最大答案
尽量向右

while(l < r)
{
    mid = l + r + 1 >> 1;
    if(Check(mid))
        l = mid;
    else
        r = mid - 1;
}

三分查找
凸函数找凸点

int TPS(int l, int r) //找凸点
{
    int mid, mmid;
    while(l < r-1)
    {
        mid = l+r>>1, mmid = mid+r>>1;
        if(f(mid) > f(mmid)) r = mmid;
        else l = mid;
    }
    return f(l) > f(r) ? l : r;
}

对拍

目录下有两个要拍的程序x1.exe和x2.exe,还有数据生成器 data.exe,运行对拍程序开始对拍,如果不一样停止对拍,目录下data.txt是俩程序不一样的数据。

#include<iostream>
#include<windows.h>
using namespace std;
int main()
{
    int t = 10;
    while(t--)
    {
        system("data.exe > data.txt");
        system("x1.exe < data.txt > x1.txt");
        system("x2.exe < data.txt > x2.txt");
        if(system("fc x1.txt x2.txt"))   break;
    }
    if(t == 0) cout<<"no error"<<endl;
    else cout<<"error"<<endl;
    return 0;
}

一些可能用得到的函数

builtin位运算

__builtin_ffs(x) //x最后一个1是从后向前第几位
__builtin_popcount(x) //x中1的个数
__builtin_ctz(x) //x末尾0的个数(x不能是0)
__builtin_clz(x) //x开头0的个数(x不能是0)
__builtin_parity(x) //x二进制下1个数的奇偶性

__int128读写

__int128 read()
{
    __int128 x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')f=-1,ch=getchar();
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return f*x;
}
void print(__int128 x)
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)print(x/10);//注意这里是x>9不是x>10 (2019.10 wa哭了回来标记一下)
    putchar(x%10+'0');
}
1 图论 3 1.1 术语 3 1.2 独立集、覆盖集、支配集之间关系 3 1.3 DFS 4 1.3.1 割顶 6 1.3.2 桥 7 1.3.3 强连通分量 7 1.4 最小点基 7 1.5 拓扑排序 7 1.6 欧拉路 8 1.7 哈密顿路(正确?) 9 1.8 Bellman-ford 9 1.9 差分约束系统(用bellman-ford解) 10 1.10 dag最短路径 10 1.11 二分图匹配 11 1.11.1 匈牙利算法 11 1.11.2 KM算法 12 1.12 网络流 15 1.12.1 最大流 15 1.12.2 上下界的网络的最大流 17 1.12.3 上下界的网络的最小流 17 1.12.4 最小费用最大流 18 1.12.5 上下界的网络的最小费用最小流 21 2 数论 21 2.1 最大公约数gcd 21 2.2 最小公倍数lcm 22 2.3 快速幂取模B^LmodP(O(logb)) 22 2.4 Fermat小定理 22 2.5 Rabin-Miller伪素数测试 22 2.6 Pollard-rho 22 2.7 扩展欧几里德算法extended-gcd 24 2.8 欧拉定理 24 2.9 线性同余方程ax≡b(mod n) 24 2.10 中国剩余定理 25 2.11 Discrete Logging(BL == N (mod P)) 26 2.12 N!最后一个不为0的数字 27 2.13 2^14以内的素数 27 3 数据结构 31 3.1 堆(最小堆) 31 3.1.1 删除最小值元素: 31 3.1.2 插入元素和向上调整: 32 3.1.3 堆的建立 32 3.2 并查集 32 3.3 树状数组 33 3.3.1 LOWBIT 33 3.3.2 修改a[p] 33 3.3.3 前缀和A[1]+…+A[p] 34 3.3.4 一个二维树状数组的程序 34 3.4 线段树 35 3.5 字符串 38 3.5.1 字符串哈希 38 3.5.2 KMP算法 40 4 计算几何 41 4.1 直线交点 41 4.2 判断线段相交 41 4.3 三点外接圆圆心 42 4.4 判断点在多边形内 43 4.5 两圆交面积 43 4.6 最小包围圆 44 4.7 经纬度坐标 46 4.8 凸包 46 5 Problem 48 5.1 RMQ-LCA 48 5.1.1 Range Minimum Query(RMQ) 49 5.1.2 Lowest Common Ancestor (LCA) 53 5.1.3 Reduction from LCA to RMQ 56 5.1.4 From RMQ to LCA 57 5.1.5 An<O(N), O(1)> algorithm for the restricted RMQ 60 5.1.6 An AC programme 61 5.2 最长公共子序列LCS 64 5.3 最长上升子序列/最长不下降子序列(LIS) 65 5.3.1 O(n^2) 65 5.3.2 O(nlogn) 66 5.4 Joseph问题 67 5.5 0/1背包问题 68 6 组合数学相关 69 6.1 The Number of the Same BST 69 6.2 排列生成 71 6.3 逆序 72 6.3.1 归并排序求逆序 72 7 数值分析 72 7.1 二分法 72 7.2 迭代法(x=f(x)) 73 7.3 牛顿迭代 74 7.4 数值积分 74 7.5 高斯消元 75 8 其它 77
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值