我要复习所有的模板

异或多重集树哈希

const ull mask=std::chrono::steady_clock::now().time_since_epoch().count();
int ull shift(ull x) {
	x^=mask, x^=x<<13, x^=x>>7, x^=x<<17, x^=mask;
	return x;
}
void dp(int x,int fa) {
	int ms=0; siz[x]=1;
	for(int y:to[x]) if(y^fa) dp(y,x), siz[x]+=siz[y], ms=max(ms,siz[y]);
	ms=max(ms,n-siz[x]);
	if(ms<=n/2) heart.pb(x);
}
void Hash(int x,int fa) {
	hsh[x]=1;
	for(int y:to[x]) if(y^fa) Hash(y,x), hsh[x]+=shift(hsh[y]);
	trees.insert(hsh[x]);
}
heart.clear(), dp(1,0);
for(int rt:heart) {
	trees.clear(), Hash(rt,0);
	qwq=max(trees,qwq);


质数树哈希

int vis[M], p[M], top;
void init() {
	int d=1299709;
	up(i,2,d) if(!vis[i]) {
		vis[i]=1, p[++top]=i;
		up(j,2,d/i) vis[i*j]=1;
	}
}
struct Tree {
	int n, siz[N]; ull hsh[N]; vector<int> to[N];
	void build(int x) { n=x; }
	void eadd(int u,int v) { to[u].pb(v), to[v].pb(u); }
	void Hash(int x,int fa) {
		hsh[x]=1, siz[x]=1;
		for(int y:to[x]) if(y^fa) Hash(y,x), siz[x]+=siz[y], hsh[x]+=hsh[y]*p[siz[y]];
	}
	void change(int x,int fa,int I) {
		if(fa) hsh[x]=hsh[x]+p[n-siz[x]]*(hsh[fa]-hsh[x]*p[siz[x]]);
		for(int y:to[x]) if(y^fa) change(y,x,I);
	}
};

拓展欧几里得

我们要求一组 \(ax+by=c\) 的解 \((x,y)\),首先充要条件是 \(\gcd(x,y)|c\),于是我们先在做欧几里得的时候求解一组 \(ax+by=\gcd(a,b)\) 再简单乘即可。

\[ax_1+by_1=\gcd(a,b)=bx_2+(a\mod b)y_2 \]

\[=bx_2+(a-b\lfloor\frac{a}{b}\rfloor)y_2=ay_2+(x_2-\lfloor\frac{a}{b}\rfloor y_2)b \]

int exgcd(int a,int b,int &x,int &y) {
	if(b==0) { x=1, y=0; return a; }
	int d=exgcd(b,a%b,x,y);
	int t=x; x=y, y=t-a/b*y;
	return d;
}

lnk,括欧还可以在模意义下部分还原分数,具体见 P10383。

\(g=\text{lcm}(a,b)\),求出一组特解 \(ax_0+by_0=c\),那么一元二次的通解形式是 \(a(x_0+\frac{g}{b}k)+b(y_0-\frac{g}{a}k)=c\)


线性筛素数

埃氏筛一个合数被标记次数没有严格保证,我们考虑让一个合数只被标记一次,贡献方式为「\(最小质因子\times 常数\)」,那么一个常数要去成所有不超过其最小质因子的素数去贡献。

int n, q, p[M], top; bool vis[N];
up(i,2,n) {
	if(!vis[i]) p[++top]=i;
	for(int j=1; j<=top&&i*p[j]<=n; ++j) {
		int x=i*p[j]; vis[x]=1;
		if(i%p[j]==0) break;
	}
}

在素数的位置带 \(\log\) 总的复杂度仍然是 \(O(n)\),因为质数的幂总共只有 \(\frac{n}{\log n}\) 那么多。


欧拉筛线性递推 欧拉/莫比乌斯 函数

分别照着定义式递推即可,代码包含在杜教筛那里 qwq

\[\phi(n)=n\prod_{p 是 n 的质因子}\frac{p-1}{p} \]

\[\mu(n)=\begin{cases}(-1)^{n 的质因子数量}&n没有平方因子\\0&otherwise\end{cases} \]


杜教筛

具体过程如 lnk,因为年代并不久远不再赘述。

数论函数求前缀和可以套杜教筛公式,计算 \(S(n)=\sum_{i=1}^n f(i)\),不妨构造 \(h(i)=\sum_{d|i}f(d)g(\frac{n}{d})\),然后套用公式 \(g(1)S(n)=\sum_{i=1}^n h(i)-\sum_{i=2}^n g(i)S(\lfloor\frac{n}{i}\rfloor)\),整除分块+记忆化复杂度 \(O(n^{\frac{3}{4}})\),加上到 \(n^{\frac{2}{3}}\) 的预处理可以做到 \(O(n^{\frac{2}{3}})\)

思想大概是什么考虑构造卷积,然后考虑 \(f\)\(g\) 的贡献发现是前缀和的形式,欧拉函数和莫比乌斯函数用 \(n=\sum_{d|n}\phi(d),[n=1]=\sum_{d|n}\mu(d)\) 来做(

int t, n, k=5e6;
int vis[N], p[N], top, phi[N], mu[N];
map<int,int> sphi, smu;
void init() {
	vis[0]=vis[1]=phi[1]=mu[1]=1;
	up(i,2,k) {
		if(!vis[i]) p[++top]=i, phi[i]=i-1, mu[i]=-1;
		for(int j=1; j<=top&&i*p[j]<=k; ++j) {
			int x=i*p[j]; vis[x]=1;
			if(i%p[j]) mu[x]=-mu[i], phi[x]=phi[i]*(p[j]-1);
			else { mu[x]=0, phi[x]=phi[i]*p[j]; break; } 
		}
	}
	up(i,1,k) phi[i]+=phi[i-1], mu[i]+=mu[i-1];
}
int getphi(int x) {
	if(x<=k) return phi[x];
	if(sphi.find(x)!=sphi.end()) return sphi[x];
	int res=(1+x)*x/2;
	for(int l=2, r; l<=x; l=r+1) {
		r=min(x,x/(x/l));
		res-=(r-l+1)*getphi(x/l);
	}
	return sphi[x]=res;
}
int getmu(int x) {
	if(x<=k) return mu[x];
	if(smu.find(x)!=smu.end()) return smu[x];
	int res=1;
	for(int l=2, r; l<=x; l=r+1) {
		r=min(x,x/(x/l));
		res-=(r-l+1)*getmu(x/l); 
	}
	return smu[x]=res;
}

康托展开

\[A_i=\sum_{j=i}^n[a_j<a_i],Ans=1+\sum_{i=1}^n A_i\times (n-i)! \]

考虑比 \(a\) 小的排列数,枚举 \(1,\dots,i-1\) 位相同,那么 \(a'_i<a_i\),从后面拉一个上来,然后剩下的可以随便排列。


差分约束

考虑一堆方程组形如 \(x_i+d\geq x_j\),不妨写成 \(i\to j:d\) 去跑最短路,无解就是存在负环(有点 \(入队次数>n\))。

注意最短路去跑具有极大性,就是说每一个 \(x\) 其实都尽量往极值去取了,天生极端化了字典序。


dfs spfa 判负环

负环从某个位置开始走可以一直都是负的,我们可以用这个来优化常数,有些题目(比如 P3199)下这样判断跑的飞快。

int spfa(int x) {
	vis[x]=1;
	for(pii p:to[x]) {
		int y=p.first; db val=p.second;
		if(dis[x]+val<dis[y]) {
			dis[y]=dis[x]+val;
			if(vis[y]||spfa(y)) return 1;
		}
	}
	vis[x]=0;
	return 0;
}
bool check() {
	up(i,1,n) dis[i]=vis[i]=0;
	up(i,1,n) if(spfa(i)) return 1;
	return 0;
}

无向图 割边/割点 判定法则

考虑随便搜出一个 dfs 树,一个 边/点 是不是割就看有没有跨越的东西就行了,为了方便,不妨令 \(dfn/low\) 表示 时间戳/能回溯的最小时间戳,这样只用比大小就能判断了,注意割边要判重。

一条边 \((x,y)\)\(low[y]>dfn[x]\) 是为割边,一个点在有儿子 \(y\) 使得 \(low[y]\geq dfn[x]\) 时是割点,但是注意特判根,根的判定是具有多个儿子。


边双联通分量

去掉割边然后算联通即可,然后重新建图是简单东西。

#define num(x) ((x+1)>>1)
int n, m, tot, dfn[N], low[N], stamp, cut[M], gp[N], cnt;
vector<pair<int,int> > to[N];
void tarjan(int x,int lnk) {
	dfn[x]=low[x]=++stamp;
	for(pii p:to[x]) {
		int y=p.first, i=p.second;
		if(num(i)==num(lnk)) continue;
		if(dfn[y]) low[x]=min(low[x],dfn[y]);
		else {
			tarjan(y,i), low[x]=min(low[x],low[y]);
			if(low[y]>dfn[x]) cut[num(i)]=1;
		}
	}
}
void dfs(int x) {
	for(pii p:to[x]) {
		int y=p.first, i=p.second;
		if(cut[num(i)]||gp[y]) continue;
		gp[y]=cnt, dfs(y); 
	}
}
cin >> n >> m;
up(i,1,m) {
	int u, v;
	cin >> u >> v;	
	to[u].pb(mp(v,++tot));
	to[v].pb(mp(u,++tot));
}
up(i,1,n) if(!dfn[i]) tarjan(i,0);
up(i,1,n) if(!gp[i]) gp[i]=++cnt, dfs(i);

点双联通分量、广义圆方树

首先点双在搜索栈里面考虑就行了,建图可以使用广义圆方树,就是对于每一个点双建立一个方点,然后向所在的分量的圆点连边,这样子可以保证性质不变,注意方点要开两倍空间,重边要特判。

image

int n, m, dfn[N], low[N], tot, stamp, stk[N], top; 
vector<int> F[N], G[N<<1]; 
void tarjan(int x) {
	dfn[x]=low[x]=++stamp, stk[++top]=x;
	for(int y:F[x]) {
		if(dfn[y]) low[x]=min(low[x],dfn[y]);
		else {
			tarjan(y), low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x]) {
				++tot; int p;
				while(stk[top+1]!=y) {
					p=stk[top--];
					G[p].pb(tot), G[tot].pb(p);
				}
				G[tot].pb(x), G[x].pb(tot);
			}
		}
	}
}
cin >> n >> m, tot=n;
up(i,1,m) {
	int u, v; cin >> u >> v;
	if(u!=v) F[u].pb(v), F[v].pb(u);
}
up(i,1,n) if(!dfn[i]) tarjan(i);

强联通分量

其实可以不写 tarjan,正图做一次 dfs 记录回溯时间压入栈,按照回溯时间大到小在反图上搜出强联通分量,应该很好理解。

int n, m, stk[N], top, gp[N], cnt, vis[N], p;
vector<int> F[N], G[N];
void dfs(int x) {
	vis[x]=1;
	for(int y:F[x]) if(!vis[y]) dfs(y);
	stk[++top]=x;
}
void stain(int x) {
	for(int y:G[x]) if(!gp[y]) gp[y]=cnt, stain(y);
}
cin >> n >> m;
up(i,1,m) {
	int u, v; cin >> u >> v;
	F[u].pb(v), G[v].pb(u);
}
up(i,1,n) if(!vis[i]) dfs(i);
dn(i,n,1) if(!gp[p=stk[i]]) gp[p]=++cnt, stain(p);

网络最大流

建出反边,每次找出增广路流,是一个分层图最短路。

注意有些题目需要 \(\infty\) 的连边,但是不能给初始流量的 \(\infty\uparrow\) 不然会不够流。

int n, m, s, t, dis[N], maxflow, cur[N];
int hd[N], nxt[M], eg[M], to[M], tot=1, fl;
queue<int> q;
void eadd(int u,int v,int w) {
	to[++tot]=v, eg[tot]=w;
	nxt[tot]=hd[u], hd[u]=tot;
}
bool bfs() {
	up(i,1,n) dis[i]=0;
	while(q.size()) q.pop(); 
	dis[s]=1, q.push(s);
	while(q.size()) {
		int x=q.front(); q.pop();
		for(int i=hd[x]; i; i=nxt[i]) {
			int y=to[i], w=eg[i];
			if(w&&!dis[y]) dis[y]=dis[x]+1, q.push(y);
		}
	}
	return dis[t];
}
int dfs(int x,int sum) {
	if(x==t) return sum; 
	int res=0;
	for(int i=cur[x]; i&&sum; i=nxt[i]) {
		cur[x]=i;
		int y=to[i], w=eg[i];
		if(w&&dis[x]+1==dis[y]) {
			int k=dfs(y,min(sum,w));
			if(!k) dis[y]=0;
			eg[i]-=k, eg[i^1]+=k, res+=k, sum-=k;
		}
	}
	return res;
}
int getflow() {
	while(bfs()) {
		up(i,1,n) cur[i]=hd[i];
		maxflow+=dfs(s,inf); 
	}
	return maxflow;
}

费用流

int n, m, s, t, dis[N], in[N], maxflow, mincost;
int hd[N], nxt[M], eg[M], to[M], lv[M], tot=1, cur[N];
queue<int> q;
inline void fadd(int u,int v,int w,int l) { to[++tot]=v, eg[tot]=w, lv[tot]=l, nxt[tot]=hd[u], hd[u]=tot; }
void eadd(int u,int v,int w,int l) { fadd(u,v,w,l), fadd(v,u,0,-l); } 
int spfa() {
	int flag=0;
	up(i,1,n) dis[i]=inf;
	dis[s]=0, q.push(s), in[s]=1;
	while(q.size()) {
		int x=q.front(); q.pop();
		flag|=(x==t), in[x]=0;
		for(int i=hd[x]; i; i=nxt[i]) {
			int y=to[i], w=eg[i], v=lv[i];
			if(w&&dis[x]+v<dis[y]) {
				dis[y]=dis[x]+v;
				if(!in[y]) in[y]=1, q.push(y);
			}
		}
	}
	return flag;
}
int dfs(int x,int sum) {
	if(x==t) return sum; 
	in[x]=1; int res=0;
	for(int i=cur[x]; i&&sum; i=nxt[i]) {
		cur[x]=i;
		int y=to[i], w=eg[i], v=lv[i];
		if(!in[y]&&w&&dis[x]+v==dis[y]) {
			int k=dfs(y,min(sum,w));
			if(k) eg[i]-=k, eg[i^1]+=k, res+=k, sum-=k, mincost+=k*v;
		}
	}
	in[x]=0;
	return res;
}
int getflow() {
	while(spfa()) {
		up(i,1,n) cur[i]=hd[i];
		maxflow+=dfs(s,inf);
	}
}

FFT

#include<bits/stdc++.h>
#define db double
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)

using namespace std;

const db pi=acos(-1);
const int N=2145141;

int n, m, tr[N];

struct cp { db x, y; } f[N], g[N], sav[N];
typedef const cp ccp;
cp operator+(ccp &a,ccp &b) { return (cp){a.x+b.x,a.y+b.y}; }
cp operator-(ccp &a,ccp &b) { return (cp){a.x-b.x,a.y-b.y}; }
cp operator*(ccp &a,ccp &b) { return (cp){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x}; }

void fft(cp *f,bool op) {
	up(i,0,n-1) if(i<tr[i]) swap(f[i],f[tr[i]]);
	for(int p=2; p<=n; p<<=1) {
		int len=p>>1;
		cp tG=(cp){cos(2*pi/p),sin(2*pi/p)};
		if(!op) tG.y*=-1;
		for(int k=0; k<n; k+=p) {
			cp buf=(cp){1,0};
			up(l,k,k+len-1) {
				cp tt=buf*f[len+l];
				f[len+l]=f[l]-tt, f[l]=f[l]+tt;
				buf=buf*tG;
			}
		} 
	}
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	up(i,0,n) cin >> f[i].x;
	up(i,0,m) cin >> g[i].x;
	for(m+=n, n=1; n<=m; n<<=1);
	up(i,0,n-1) tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0);
	fft(f,1), fft(g,1);
	up(i,0,n-1) f[i]=f[i]*g[i];
	fft(f,0);
	up(i,0,m) cout << (int)(f[i].x/n+0.49) << ' ';
	return 0;
}

FFT(三次变两次)

#include<bits/stdc++.h>
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
#define db double

using namespace std;

const db pi=acos(-1);
struct cp { db x, y; };
typedef const cp ccp;
cp operator+(ccp &a,ccp &b) { return (cp){a.x+b.x,a.y+b.y}; }
cp operator-(ccp &a,ccp &b) { return (cp){a.x-b.x,a.y-b.y}; }
cp operator*(ccp &a,ccp &b) { return (cp){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x}; }
vector<cp> w[20];

void init(int m) {
	for(int i=1; (1<<i)<=m; ++i) {
		int n=(1<<i); w[i].resize(n>>1);
		up(j,0,(n>>1)-1) w[i][j]=(cp){cos(2*j*pi/n),sin(2*j*pi/n)}; 
	}
}

void dft(vector<cp> &f) {
	int n=f.size();
	if(n==1) return;
	vector<cp> f0(n>>1), f1(n>>1);
	up(i,0,n-1) if(i&1) f1[i>>1]=f[i]; else f0[i>>1]=f[i]; 
	dft(f0), dft(f1);
	int id=0; while((1<<id)<n) ++id;
	up(i,0,(n>>1)-1) {
		cp tmp=w[id][i]*f1[i];
		f[i]=f0[i]+tmp, f[i+(n>>1)]=f0[i]-tmp;
	}
}

void idft(vector<cp> &f) {
	int n=f.size();
	dft(f), reverse(&f[1],&f[n]);
	up(i,0,n-1) f[i].x/=n, f[i].y/=n;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n, m, N=1;
	cin >> n >> m;
	while(N<=n+m) N<<=1;
	init(N); vector<cp> f(N);
	up(i,0,n) cin >> f[i].x;
	up(i,0,m) cin >> f[i].y;
	dft(f);
	up(i,0,N-1) f[i]=f[i]*f[i];
	idft(f);
	up(i,0,n+m) {
		int res=f[i].y*0.5+0.1;
		cout << res << ' ';
	}
	return 0;
}

NTT

lnk,常见素数及其原根。

#include<bits/stdc++.h>
#define int long long
#define db long double
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)

using namespace std;

const int P=998244353, G=3, N=1350000;

int ksm(int a,int b=P-2) {
	int res=1;
	for( ; b; b>>=1) {
		if(b&1) res=res*a%P;
		a=a*a%P;
	}
	return res;
}

int n, m, tr[N<<1], f[N<<1], g[N<<1], invn, invG=ksm(G);

void NTT(int *f,bool op) {
	up(i,0,n-1) if(i<tr[i]) swap(f[i],f[tr[i]]);
	for(int p=2; p<=n; p<<=1) {
		int len=p>>1, tG=ksm(op?G:invG,(P-1)/p);
		for(int k=0; k<n; k+=p) {
			int buf=1;
			up(l,k,k+len-1) {
				int tt=buf*f[len+l]%P;
				f[len+l]=f[l]-tt;
				if(f[len+l]<0) f[len+l]+=P;
				f[l]=f[l]+tt;
				if(f[l]>P) f[l]-=P;
				buf=buf*tG%P; 
			}
		}
	}
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m, ++n, ++m;
	up(i,0,n-1) cin >> f[i];
	up(i,0,m-1) cin >> g[i];
	for(m+=n, n=1; n<m; n<<=1);
	up(i,0,n-1) tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0);
	NTT(f,1), NTT(g,1);
	up(i,0,n-1) f[i]=f[i]*g[i]%P;
	NTT(f,0), invn=ksm(n);
	up(i,0,m-2) cout << f[i]*invn%P << ' '; 
	return 0;
}

字符串考我就摆烂

posted @ 2024-05-13 16:12  Hypoxia571  阅读(11)  评论(0编辑  收藏  举报