提高组所有知识模板大合集

看到哪个写哪个

排序

1.sort

2.归并排序

#include<bits/stdc++.h>
using namespace std;
int T,n,m;
int d[2000005],g[2000005],top;
void merge(int l,int r,int *c){
	int mid=l+r>>1;
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r){
		if(d[i]<d[j]) c[k++]=d[i++];
		else c[k++]=d[j++];
	}
	while(i<=mid) c[k++]=d[i++];
	while(j<=r) c[k++]=d[j++];
}
void solve(int l,int r){
	if(l==r) return ;
	if(r-l==1){
		if(d[l]>d[r]) swap(d[l],d[r]);
		return ;
	}
	int mid=l+r>>1;
	solve(l,mid);
	solve(mid+1,r);
	merge(l,r,g);
	for(int i=l;i<=r;i++) d[i]=g[i];
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>d[i];
	solve(1,n);
	for(int i=1;i<=n;i++) cout<<g[i]<<' ';
	return 0;
}

3.堆排序

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,len,heap[N];
void push(int x){
	if(x==1||heap[x>>1]<=heap[x]) return;
	swap(heap[x>>1],heap[x]),push(x>>1);
}
void pop(int x){
	int y=x;
	if((x<<1)<=len&&heap[x<<1]<heap[x]) y=x<<1;
	if((x<<1|1)<=len&&heap[x<<1|1]<heap[y]) y=x<<1|1;
	if(y!=x) swap(heap[x],heap[y]),pop(y);
}
void solve(){
	cin>>n;
	while(len<n) cin>>heap[++len],push(len);
	while(len) cout<<heap[1]<<' ',heap[1]=heap[len--],pop(1);
}
signed main(){
	solve();
	return 0;
}

暂时只记录这三种

最短路

1.Dijkstra

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int he[N],net[N<<1],vre[N<<1],w[N<<1];
int vis[N],dis[N];
int n,m,tot;
struct node {
	int ds,p;
};
bool operator <(node a,node b) {
	return a.ds>b.ds;
}
inline void add(int x,int y,int z) {//邻接表记录
	++tot;
	w[tot]=z;
	vre[tot]=y;
	net[tot]=he[x];
	he[x]=tot;
}
priority_queue<node> q;
inline void Dijkstra(int x) {
	memset(dis,0x3f,sizeof dis);
	dis[x]=0;
	q.push({0,x});
	while(q.size()) {
		node tmp=q.top();
		q.pop();
		int y=tmp.p;
		vis[y]=1;
		for(int i=he[y]; i; i=net[i]) {
			int u=vre[i];
			if(!vis[u]&&dis[u]>dis[y]+w[i]) {
				dis[u]=dis[y]+w[i];
				q.push({dis[u],u});
			}
		}
	}
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	for(int i=1; i<=m; ++i) {
		int x,y,z;
		cin>>x>>y>>z;
		add(x,y,z);
	}
	Dijkstra(1);
	if(dis[n]==0x3f3f3f3f) {//没被更新
		cout<<-1;
		return 0;
	}
	cout<<dis[n];
	return 0;
}

2.SPFA

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int he[N],net[N<<1],vre[N<<1],w[N<<1];
int vis[N],dis[N];
int n,m,tot;
inline void add(int x,int y,int z){
	++tot;
	w[tot]=z;
	vre[tot]=y;
	net[tot]=he[x];
	he[x]=tot;
}
queue<int> q;
inline void SPFA(int x){
	memset(dis,0x3f,sizeof dis);
	dis[x]=0;
	vis[x]=1;
	q.push(1);
	while(!q.empty()){
		int y=q.front();
		q.pop();
		vis[y]=0;
		for(int i=he[y];i;i=net[i]){
			int u=vre[i];
			if(dis[u]>dis[y]+w[i]){
				dis[u]=dis[y]+w[i];
				if(vis[u]==0) {
					vis[u]=1;
					q.push(u);
				}
			}
		}
	}
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		add(x,y,z);
	}
	SPFA(1);
	if(dis[n]>=0x3f3f3f3f){
		cout<<-1;
	}
	else cout<<dis[n];
	return 0;
}

3.Floyd

for(int k=1;k<=n;k++)
  for(int i=1;i<=n;i++)
  	for(int j=1;j<=n;j++)
    	d[i][j]=min(d[i][j],d[i][k]+d[k][j]);  		

字符串

好像只会KMP

1.KMP

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
string u,v;
int ne[N],f[N];
int main(){
	cin>>u>>v;
    int n=u.size(),m=v.size();
    u=' '+u,v=' '+v;
    ne[1]=0;
    for(int i=2,j=0;i<=m;i++){
        while(j && v[i]!=v[j+1])j=ne[j];
        if(v[i]==v[j+1])j++;
        ne[i]=j;
    }
    for(int i=1,j=0;i<=n;i++){
        while(j && u[i]!=v[j+1])j=ne[j];
        if(u[i]==v[j+1])j++;
        if(j==m) cout<<i-j+1<<'\n';
    }
    for(int i=1;i<=m;i++)cout<<ne[i]<<' ';
	return 0;
}

hash

Hash 的核心思想在于,将输入映射到一个值域较小、可以方便比较的范围。

对于字符串hash
在 Hash 函数值不一样的时候,两个字符串一定不一样;
在 Hash 函数值一样的时候,两个字符串不一定一样(但有大概率一样,且我们当然希望它们总是一样的)。
我们将 Hash 函数值一样但原字符串不一样的现象称为哈希碰撞。

背包/dp

1.01背包

for(int i=1;i<=n;i++)
  for(int j=v;j>=w[i];j--)
    dp[j]=max(dp[j],dp[j-w[i]]+val[i]);    

2.完全背包

for(int i=1;i<=n;i++){
  for(int j=1;j<=vo;j++){
    if(j>=v[i])
      dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
  }
}

3.多重背包

for(int i=1;i<=n;i++)
  for(int j=v;j>=w[i];j--)
    for(int k=1;k*w[i]<=j&&k<=num[i];k++)
      dp[j]=max(dp[j],dp[j-w[i]*k]+val[i]*k); 

注意:完全背包,01背包和多重背包的不同在于选择次数的不同,同时也决定了遍历的方法

4.常见的dp模板

1.最长上升子序列 LIS

for(int i=1;i<=n;i++) dp[i]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i-1;j++)
			if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);
for(int i=1;i<=n;i++) ans=max(ans,dp[i]);	

2.最长公共子序列 LCS

for(int i=1;i<=n;i++)
  for(int j=1;j<=m;j++){
    dp[i&1][j]=max(dp[(i-1)&1][j,dp[i&1][j-1]]);
    if(a[i]==b[j]) dp[i&1][j]=max(dp[i&1][j],dp[(i-1)&1][j-1]);
  }

搜索

没有模板,只有一些剪枝方法

1.常用剪枝

  • 可行性剪枝
  • 最优性剪枝 (对于当前答案已经大于之前答案的,直接退出)
  • 记忆化 (对于已经搜过的节点进行记录)

2.迭代加深

用类似于广搜的方法去做深搜,以免深度过大但答案又在浅层带来的巨量时间复杂度。
每次设定一个深度,一但到达深度仍没有找到答案就停止。

3.A*和IDA*

A*是导航,IDA*是预警。

::::info[A*就是BFS+估价函数]
每一次BFS算出当前层的估价函数,遍历估价函数最小的。
::::
::::info[IDA*就是DFS+迭代加深+估价函数]
以迭代加深DFS的框架,将深度限制设置为: 若当前深度+未来估计步数>深度限制,则直接退出
::::

基础算法

1.快速幂和龟速乘

int ksm(int x,int k){//快速幂
    int ans=1;
    while(k){
        if(k&1) ans=ans*x%p;
        x=x*x%p;
        k>>=1;
    }
    return ans;
}
int mul(int a,int b,int mod){//龟速乘
    int ans=0;
    while(b){
        if(b&1) ans=(ans+a)%mod;
        a=(a+a)%mod,b>>=1;
    }
    return ans;
}

2.差分和前缀和

for(int i=1;i<=n;i++){
  cin>>a[i];
  sum[i]=a[i]+sum[i-1];//前缀和
  c[i]=a[i]-a[i-1];//差分
}

3.双指针

同时使用两个指针,在序列、链表结构上指向的是位置,在树、图结构中指向的是节点,通过或同向移动,或相向移动来维护、统计信息。
e.g. 维护区间信息(常用),子序列匹配

4.位运算

与,或,异或

运算 运算符 数学表达 解释
\(\&\) \(\&\),\(and\) 只有两个对应位都为 1 时才为 1
| |,\(or\) 只要两个对应位中有一个 1 时就为 1
异或 \(^\) \(\oplus,xor\) 只有两个对应位不同时才为 1

取反

取反暂无默认的数学符号表示,其对应的运算符为 ~。它的作用是把 𝑛𝑢𝑚 的二进制补码中的0 和 1 全部取反(0 变为 1,1 变为 0)。有符号整数的符号位在 ~ 运算中同样会取反。

左移和右移

\(num\) << \(i\) 表示将\(num\)的二进制表示向左移动\(i\)位所得的值。
\(num\) >> \(i\) 表示将\(num\)的二进制表示向右移动\(i\)位所得的值。
通常用\((1<<n)\)替换\(2^n\)

位运算通常用于状压DP,或对于某种状态的存储

基础数据结构

栈,堆,队列

1.栈

#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int T,n,m;
int d[2000005],top;
signed main(){
	cin>>T;
	while(T--){
		cin>>n;
		top=0;
		for(int i=1;i<=n;i++){
			string s;cin>>s;
			if(s=="push"){
				int x;cin>>x;
				d[++top]=x;
			}else if(s=="query"){
				if(top==0)cout<<"Anguei!\n";
				else cout<<d[top]<<'\n';
			}else if(s=="pop"){
				if(top==0) cout<<"Empty\n";
				else top--;
			}else {
				cout<<top<<'\n';
			}
		}
	}
	return 0;
}

2.堆排序写了

3.队列

#include<bits/stdc++.h>
using namespace std;
int n, m, i, j, k;
queue<int> q;
int main(){
    cin>>n;
    for(i=1;i<=n;i++){
        int op,x;
        cin>>op;
        if(op==1) cin>>x,q.push(x);
        else if (op == 2){
            if (q.empty()) cout<<"ERR_CANNOT_POP\n";
            else q.pop();
        }
        else if(op==3){
            if(q.empty()) cout<<"ERR_CANNOT_QUERY\n";
            else cout<<q.front()<<'\n';
        }
        else cout<<q.size()<<'\n';
    }
    return 0;
}

数学

come here

dp优化

倍增与数据结构

单调队列与斜率优化

四边形不等式与决策单调性

计数dp、数位dp(记忆化搜索)

概率期望dp

图论

LCA

int lca(int a,int b){
	if(dep[a]<dep[b]) swap(a,b);
	for(int i=22;i>=0;i--)
		if(dep[f[a][i]]>=dep[b]) a=f[a][i];
	if(a==b) return a;
	for(int i=22;i>=0;i--)
		if(f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i];
	return f[a][0];
}

树链剖分

#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int s=0,f=1;char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0' && ch<='9'){s=s*10+ch-'0';ch=getchar();}
    return s*f; 
}
const int N=5e5+10;
int n,m,rt,cnt,idx,a[N],M,sz[N],dep[N],f[N],son[N],top[N];
vector<int> v[N];
int id[N];//存储链上的新序号
int w[N];//存贮以链为序号下标的权值 
int sum[N],tag[N];
int res;
void build(int p,int l,int r){
    if(l==r){
        sum[p]=w[l]%M;
        return ;
    }
    int mid=l+r>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    sum[p]=(sum[p<<1]+sum[p<<1|1])%M;
}
void pushdown(int p,int l,int r){
    if(tag[p]){
        int mid=l+r>>1;
        int left=p<<1;
        int right=p<<1|1;
        tag[left]=(tag[left]+tag[p])%M;
        tag[right]=(tag[right]+tag[p])%M;
        sum[left]=(sum[left]+(mid-l+1)*tag[p]%M)%M;
        sum[right]=(sum[right]+(r-mid)*tag[p]%M)%M;
        tag[p]=0;
    }
} 
void change(int p,int l,int r,int x,int y,int k){
    if(x<=l && r<=y){
        tag[p]=(tag[p]+k)%M;
        sum[p]=(sum[p]+(r-l+1)*k)%M;
        return ;
    }
    pushdown(p,l,r);
    int mid=l+r>>1;
    if(x<=mid) change(p<<1,l,mid,x,y,k);
    if(y>mid) change(p<<1|1,mid+1,r,x,y,k);
    sum[p]=(sum[p<<1]+sum[p<<1|1])%M;
} 
int ask(int p,int l,int r,int x,int y){
    if(x<=l && r<=y){
        return sum[p]%M;
    }
    pushdown(p,l,r);
    int mid=l+r>>1,res=0;
    if(x<=mid) res=(res+ask(p<<1,l,mid,x,y))%M;
    if(y>mid) res=(res+ask(p<<1|1,mid+1,r,x,y))%M;
    return res%M;
}
void chan(int p,int l,int r,int x,int y){
    if(x<=l && r<=y){
        res=(res+sum[p])%M;
        return ;
    }
    pushdown(p,l,r);
    int mid=l+r>>1;
    if(x<=mid) chan(p<<1,l,mid,x,y);
    if(y>mid) chan(p<<1|1,mid+1,r,x,y);
}
void update(int x,int y,int k){
    k%=M;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        change(1,1,n,id[top[x]],id[x],k);
        x=f[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    change(1,1,n,id[x],id[y],k);
}
int query(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y); 
        res=0;
        chan(1,1,n,id[top[x]],id[x]);
        ans=(ans+res)%M;
        x=f[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    res=0;
    chan(1,1,n,id[x],id[y]);
    ans=(ans+res)%M;
    return ans;
}
void dfs1(int x,int fa){
    f[x]=fa;
    sz[x]=1;
    dep[x]=dep[fa]+1;
    int maxs=-1;
    for(int i=0;i<v[x].size();i++){
        int y=v[x][i];
        if(y==fa) continue;
        dfs1(y,x);
        sz[x]+=sz[y];
        if(sz[y]>=maxs) son[x]=y,maxs=sz[y];//处理重儿子 
    }
}
void dfs2(int x,int topp){//topp表示当前链最顶端的 
    id[x]=++cnt; 
    w[cnt]=a[x]; 
    top[x]=topp;
    if(!son[x]) return ;
    dfs2(son[x],topp);//先跑重儿子 
    for(int i=0;i<v[x].size();i++){
        int y=v[x][i];
        if(y==f[x] || y==son[x]) continue;
        dfs2(y,y);
    }
}
signed main() {
    n=read(),m=read(),rt=read(),M=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        v[x].push_back(y);
        v[y].push_back(x);
    }
    dfs1(rt,0);
    dfs2(rt,rt);
    build(1,1,n);
    int opt,x,y,z;
    while(m--){
        opt=read();
        if(opt==1){
            x=read(),y=read(),z=read();
            // 将树从 x 到 y 结点最短路径上所有节点的值都加上 z。 
            update(x,y,z);
        }
        else if(opt==2){
            x=read(),y=read();
            // 查询求树从 x 到 y 结点最短路径上所有节点的值之和。
            cout<<query(x,y)<<'\n';
        }
        else if(opt==3){
            x=read(),z=read();
            // 将以x为根的子树所有节点值加上z
            change(1,1,n,id[x],id[x]+sz[x]-1,z);
        }
        else if(opt==4){
            x=read();
            // 查询以x为根的子树所有节点值之和
            cout<<ask(1,1,n,id[x],id[x]+sz[x]-1)<<'\n';
        } 
    }
    return 0;
}

tarjan与强连通分量

tarjan 模板

void tarjan(int x){
	low[x]=dfn[x]=++idx;
	ins[x]=1;
	st[++top]=x;
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(ins[y]) low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		int y;cnt++;
		do{
			y=st[top--];
			scc[y]=cnt;
			ins[y]=0;
			sz[cnt]++;
		}while(y!=x);
		if(sz[cnt]>1) ans++;
	}
}

缩点

加上这几句就可以了

for(int i=0;i<v.size();i++){
		int x=v[i].x,y=v[i].y;
		if(scc[x]!=scc[y]) 
		    add(scc[x],scc[y]),du[scc[y]]++;
	}

割点(割顶)

void tarjan(int x){
	low[x]=dfn[x]=++idx;
	int son=0;
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(!dfn[y]){
			son++;
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(low[y]==dfn[x])
				if(x!=root || son>1) c[x]=1;//记录他是割点
		}else low[x]=min(low[x],dfn[y]);
	}
}

边双/点双连通分量

边双连通分量

//注意: add的tot要从1开始
void tarjan(int x,int com){
	low[x]=dfn[x]=++idx;
	st[++top]=x;
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(!dfn[y]){
			tarjan(y,i);
			low[x]=min(low[x],low[y]);
		}
		else if((i^1)!=com)//防止是上一条边
		    low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		int y;cnt++;
		do{
			y=st[top--];
			edcc[y]=cnt;
		}while(y!=x);
	}
}

点双连通分量

//注意: add的tot要从1开始
void tarjan(int x){
	low[x]=dfn[x]=++idx;
	st[++top]=x;
	if(!head[x]){
		v[++cnt].push_back(x);
	} 
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x]){//直接在这里更新(也可以拿到外面)
				cnt++;
				int z;
				while(true){
					z=st[top--];
					vdcc[z]=cnt;
					v[cnt].push_back(z);
					if(z==y) break;
				}
				v[cnt].push_back(x);
			}
		}
		else  low[x]=min(low[x],dfn[y]);
	}
}

2-SAT适定性问题

模板

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int s=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
	return s*f;
} 
const int N=2e6+5;
int n,m;
int ver[N],head[N],ne[N],tot=0;
void add(int x,int y){
	ver[++tot]=y,ne[tot]=head[x],head[x]=tot;
}
int dfn[N],low[N],scc[N],st[N],top,idx,cnt;
void tarjan(int x){
	dfn[x]=low[x]=++idx;
	st[++top]=x;
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}else if(!scc[y]) low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		int y;++cnt;
		do{
			y=st[top--];
			scc[y]=cnt;
		}while(y!=x);
	}
}
int main(){
	n=read(),m=read();
	for(int a=1;a<=m;a++){
		int i=read(),x=read(),j=read(),y=read();
		add(i+(!x)*n,j+y*n);
		add(j+(!y)*n,i+x*n);
	}
	for(int i=1;i<=2*n;i++){
		if(!dfn[i]) tarjan(i);
	}
	for(int i=1;i<=n;i++)
		if(scc[i]==scc[i+n]){
			cout<<"IMPOSSIBLE";
			return 0;
		}
	cout<<"POSSIBLE\n";
	for(int i=1;i<=n;++i){
		if(scc[i+n]<scc[i]) printf("1 ");
		else printf("0 ");
	}
	return 0;
}

二分图

判定二分图

染色法:

bool dfs(int x){
	vis[x]=1;
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(vis[y]){
			if(c[y]==c[x]) return 0;
		}
		else {
			c[y]=c[x]^1;
			if(!dfs(y)) return 0;
		}
	}
	return 1;
}

二分图最大匹配(匈牙利算法)

bool find(int x){
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(vis[y]) continue;
		vis[y]=1;
		if(!f[y] || find(f[y])){
			f[y]=x;
			return 1;
		}
	}
	return false;
} 
void Hungary(){
	for(int i=1;i<=n;i++){//枚举左端点 
		memset(vis,0,sizeof vis);
		if(find(i))	ans++;//如果找到了一条增广路		
	}
}

基环树

一个有且只有一个环的图

posted @ 2025-10-31 10:58  Austin0928  阅读(2)  评论(0)    收藏  举报