算法模板
一、动态规划
1. 状态压缩DP
AcWing 1064. 小国王
在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 12; ll f[N][1<<N][N*N]; int n,K,num[1<<N],p[1<<N],cnt; int count(int x){ int res = 0; while(x){ if(x&1)res++; x>>=1; } return res; } bool pd(int x,int y,bool flg){ if((flg&&(x&y)) || (x&(y>>1)) || (x&(y<<1)))return false; else return true; } int main(){ cin>>n>>K; for(int i = 0;i<(1<<n);++i){ num[i] = count(i); if(pd(i,i,0))p[++cnt] = i; } f[0][1][0] = 1; for(int i = 1;i<=n+1;++i){ for(int j = 1;j<=cnt;++j){ for(int k = 0;k<=K;++k){ if(num[p[j]] > k)continue; for(int t = 1;t<=cnt;++t){ if(!pd(p[j],p[t],1))continue; f[i][j][k] += f[i-1][t][k-num[p[j]]]; } } } } cout<<f[n+1][1][K]<<'\n'; return 0; }
2. 树形DP
AcWing 1072. 树的最长路径
给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。
现在请你找到树中的一条最长路径。
#include<bits/stdc++.h> using namespace std; const int N = 2e4 + 10,inf = 0x3f3f3f3f; int h[N],w[N],e[N],ne[N],idx; int n,f1[N],f2[N],g[N],id[N]; void add(int a,int b,int c){ e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++; } void pre_dfs(int u,int fa){ for(int i = h[u];~i;i=ne[i]){ int j = e[i],c = w[i]; if(j == fa)continue; pre_dfs(j,u); if(f1[j]+c>f1[u]){ f2[u] = f1[u]; f1[u] = f1[j]+c; id[u] = j; } else if(f1[j]+c>f2[u]){ f2[u] = f1[j]+c; } } } void dfs(int u,int fa){ for(int i = h[u];~i;i=ne[i]){ int j = e[i],c = w[i]; if(j == fa)continue; if(id[u] == j){ g[j] = max(g[u] + c, f2[u] + c); } else{ g[j] = max(g[u] + c, f1[u] + c); } dfs(j,u); } } int main(){ memset(h,-1,sizeof h); cin>>n; for(int i = 1;i<=n-1;++i){ int a,b,c; cin>>a>>b>>c; add(a,b,c); add(b,a,c); } pre_dfs(1,0); dfs(1,0); int res = 0; for(int i = 1;i<=n;++i){ res = max({res,f1[i]+f2[i],f1[i]+g[i]}); } cout<<res<<'\n'; return 0; }
3. 数位DP
不吉利的数字为所有含有 4 或 62 的号码。
你的任务是,对于每次给出的一个牌照号区间 [n,m],推断出交管局今后又要实际上给多少辆新的士车上牌照了。
#include<bits/stdc++.h> using namespace std; const int N = 15; int f[N][N]; void init(){ f[0][0] = 1; for(int i = 0;i<=9;++i)if(i!=4)f[1][i]++; for(int i = 2;i<N;++i){ for(int j = 0;j<=9;++j){ for(int k = 0;k<=9;++k){ if(j==4 || k==4)continue; if(j==6 && k==2)continue; f[i][j] += f[i-1][k]; } } } } int dp(int n){ if(!n)return 1; vector<int> v; while(n>0)v.push_back(n%10),n/=10; int last = 0,res = 0; for(int i = v.size() - 1;i>=0;--i){ int x = v[i]; for(int j = 0;j<x;++j){ if(j == 4)continue; if(last==6 && j==2)continue; for(int k = 0;k<=9;++k){ if(k == 4)continue; if(j==6 && k==2)continue; res += f[i][k]; } } if(x==4 || last==6&&x==2)break; last = x; if(!i)++res; } return res; } int main(){ int l,r; init(); while(cin>>l>>r&&(l||r)){ cout<<dp(r) - dp(l-1)<<'\n'; } return 0; }
4. 单调队列优化DP
AcWing 135. 最大子序和
输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。
#include<bits/stdc++.h> using namespace std; const int N = 3e5 + 10,inf = 99999999999; int a[N],s[N],q[N]; int main(){ int n,m; cin>>n>>m; for(int i = 1;i<=n;++i)cin>>a[i],s[i]=s[i-1]+a[i]; int hh = 0,tt = 0,res = -inf; for(int i = 1;i<=n;++i){ while(q[hh]<i-m)++hh; res = max(res,s[i]-s[q[hh]]); while(hh<=tt&&s[q[tt]]>=s[i])--tt; q[++tt] = i; } cout<<res; return 0; }
5. 斜率优化DP
AcWing 301. 任务安排2
有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。
机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。
从时刻 0 开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti。
另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。
也就是说,同一批任务将在同一时刻完成。
每个任务的费用是它的完成时刻乘以一个费用系数 Ci。
请为机器规划一个分组方案,使得总费用最小。
#include<bits/stdc++.h> using namespace std; const int N = 3e5 + 10; typedef long long ll; ll t[N],c[N],f[N],s,n,q[N]; int main(){ cin>>n>>s; for(int i = 1;i<=n;++i){ cin>>t[i]>>c[i]; t[i]+=t[i-1];c[i]+=c[i-1]; } int hh=0,tt=0; for(int i = 1;i<=n;++i){ while(hh<tt&&(f[q[hh+1]]-f[q[hh]])<=(s+t[i])*(c[q[hh+1]]-c[q[hh]]))++hh; int j = q[hh]; f[i] = f[j] - c[j]*(s+t[i]) + c[i]*t[i]+s*c[n]; while(hh<tt&&(f[q[tt]]-f[q[tt-1]])*(c[i]-c[q[tt]])>=(f[i]-f[q[tt]])*(c[q[tt]]-c[q[tt-1]]))--tt; q[++tt] = i; } cout<<f[n]; return 0; }
二、搜索
1.A*
AcWing 178. 第K短路
#include<bits/stdc++.h> using namespace std; #define x first #define y second const int N = 1010,inf = 0x3f3f3f3f; typedef pair<int,int> PII; typedef pair<int,PII> PIII; int n,m,f[N],S,T,K; vector<PII>v[N],vt[N]; void dijkstra(){ bool vis[N]; priority_queue<PII,vector<PII>,greater<PII> >Q; memset(f,inf,sizeof f); memset(vis,0,sizeof vis); f[T] = 0; Q.push({0,T}); while(Q.size()){ auto t = Q.top(); Q.pop(); if(vis[t.y])continue; vis[t.y] = 1; for(auto&[to,w]:vt[t.y]){ if(w+f[t.y]<f[to]){ f[to] = w + f[t.y]; Q.push({f[to],to}); } } } } int bfs(){ if(S==T)++K; int cnt[N]; memset(cnt,0,sizeof cnt); priority_queue<PIII,vector<PIII>,greater<PIII> >Q; Q.push({f[S],{0,S}}); while(Q.size()){ auto t = Q.top(); Q.pop(); cnt[t.y.y]++; int u = t.y.y,d = t.y.x; if(u==T && cnt[u]>=K)return d; for(auto&[to,w]:v[u]){ if(cnt[to]<=K) Q.push({d+w+f[to],{d+w,to}}); } } return -1; } int main(){ cin>>n>>m; for(int i = 1;i<=m;++i){ int a,b,l; cin>>a>>b>>l; v[a].push_back({b,l}); vt[b].push_back({a,l}); } cin>>S>>T>>K; dijkstra(); int ans = bfs(); cout<<ans; return 0; }
2.双向广搜
已知有两个字串 A, B 及一组字串变换的规则(至多 6 个规则):
若在 10 步(包含 10 步)以内能将 A 变换为 B ,则输出最少的变换步数;否则输出 NO ANSWER!。
#include<bits/stdc++.h> using namespace std; const int N = 110; int n = 0; string a[N],b[N]; string A,B; int extend(queue<string>&qa,unordered_map<string,int>&ma,unordered_map<string,int>&mb,string a[],string b[]){ int d = ma[qa.front()]; while(qa.size() && ma[qa.front()] == d){ string str = qa.front(); qa.pop(); for(int i = 0;i<n;++i){ for(int j = 0;j+a[i].size()<=str.size();++j){ if(str.substr(j,a[i].size())==a[i]){ string g = str.substr(0,j) + b[i] + str.substr(j+a[i].size()); if(mb.count(g))return ma[str] + mb[g] + 1; if(ma.count(g))continue; ma[g] = ma[str] + 1; qa.push(g); } } } } return 11; } int bfs(){ if(A==B){ return 0; } int step = 0; queue<string> qa,qb; qa.push(A);qb.push(B); unordered_map<string,int>ma,mb; ma[A] = mb[B] = 0; while(qa.size()&&qb.size()){ int res; if(qa.size()<qb.size())res = extend(qa,ma,mb,a,b); else res = extend(qb,mb,ma,b,a); if(res <= 10)return res; if(++step>10)break; } return -1; } int main(){ cin>>A>>B; while(cin>>a[n]>>b[n])++n; int ans = bfs(); if(ans!=-1)cout<<ans; else cout<<"NO ANSWER!"<<'\n'; return 0; }
3.IDA*
给定 n 本书,编号为 1∼n。
在初始状态下,书是任意排列的。
在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。
我们的目标状态是把书按照 1∼n 的顺序依次排列。
求最少需要多少次操作。
#pragma GCC optimize("Ofast", "inline", "-ffast-math") #pragma GCC target("avx,sse2,sse3,sse4,mmx") #include<bits/stdc++.h> using namespace std; typedef vector<int> VI; #define x first #define y second typedef pair<VI,int> P; const int N = 16; int n,a[N]; int lst[2][N]; int f(vector<int> &v,bool b){ int res = 0; for(int i = 0;i+1<n;++i){ if(v[i+1]!=lst[b][v[i]])++res; } res += (0!=lst[b][v[n-1]]); return res; } int extend(queue<P> &qa,map<VI,int>&da,map<VI,int>&db,bool b){ int d = qa.front().y; while(qa.size() && qa.front().y == d){ auto t = qa.front(); VI v = t.x; qa.pop(); if(f(v,b)+d>=5)continue; for(int l = 0;l<n;++l){ for(int r = l;r+1<n;++r){ for(int k = r+1;k<n;++k){ VI to(n); int t = 0; for(int i = 0;i<l;++i)to[t++] = v[i]; for(int i = r+1;i<=k;++i)to[t++] = v[i]; for(int i = l;i<=r;++i)to[t++] = v[i]; for(int i = k+1;i<n;++i)to[t++] = v[i]; if(da.count(to))continue; qa.push({to,d+1}); da[to] = d + 1; if(db.count(to)){ return db[to] + d + 1; } } } } } return -1; } int bfs(){ queue<P> qa,qb; map<VI,int>da,db; VI sa,sb; for(int i = 0;i<n;++i)sa.push_back(a[i]),lst[0][a[i]] = a[i+1]; for(int i = 0;i<n;++i)sb.push_back(i+1),lst[1][i+1] = i+2; lst[1][n] = 0; qa.push({sa,0}); da[sa] = 0; qb.push({sb,0}); db[sb] = 0; int dep = 0; if(sa == sb){ return 0; } while(qa.size() && qb.size()){ int d; if(qa.size()<qb.size())d = extend(qa,da,db,0); else d = extend(qb,db,da,1); if(d!=-1)return d; ++dep; if(dep==4)break; } return -1; } int main(){ //freopen("a.txt","r",stdin); int T; cin>>T; while(T--){ cin>>n; for(int i = 0;i<n;++i)cin>>a[i]; int ans = bfs(); if(ans==-1)puts("5 or more"); else cout<<ans<<'\n'; } return 0; }
三、图论
1.有向图的强连通分量
每一头牛的愿望就是变成一头最受欢迎的牛。
现在有 N 头牛,编号从 1 到 N,给你 M 对整数 (A,B),表示牛 A 认为牛 B 受欢迎。
这种关系是具有传递性的,如果 A 认为 B 受欢迎,B 认为 C 受欢迎,那么牛 A 也认为牛 C 受欢迎。
你的任务是求出有多少头牛被除自己之外的所有牛认为是受欢迎的。
#include<bits/stdc++.h> using namespace std; const int N = 10010; vector<int> G[N]; int dfn[N],low[N],stk[N],top; int scc_cnt,sz[N],dout[N],id[N]; int n,m,cnt = 0; bool is_stk[N]; void tarjan(int u){ dfn[u] = low[u] = ++cnt; stk[++top] = u; is_stk[u] = true; for(auto to:G[u]){ if(!dfn[to]){ tarjan(to); low[u] = min(low[to],dfn[u]); } else if(is_stk[to]){ low[u] = min(low[u],low[to]); } } if(dfn[u] == low[u]){ int y; scc_cnt++; do{ y = stk[top--]; is_stk[u] = false; id[y] = scc_cnt; sz[scc_cnt]++; } while(y!=u); } } bool vis[N]; int main(){ cin>>n>>m; for(int i = 1;i<=m;++i){ int a,b; cin>>a>>b; G[a].push_back(b); } for(int i = 1;i<=n;++i) if(!dfn[i])tarjan(i); for(int i = 1;i<=n;++i){ for(auto to:G[i]){ if(id[i] != id[to]) dout[id[i]]++; } } int ans = 0,zeros = 0; for(int i = 1;i<=scc_cnt;++i){ if(!dout[i]){ ++zeros; ans = sz[i]; if(zeros>1){ ans = 0; break; } } } cout<<ans; return 0; }
2.无向图的双连通分量
1)桥
为了从 F 个草场中的一个走到另一个,奶牛们有时不得不路过一些她们讨厌的可怕的树。
奶牛们已经厌倦了被迫走某一条路,所以她们想建一些新路,使每一对草场之间都会至少有两条相互分离的路径,这样她们就有多一些选择。
每对草场之间已经有至少一条路径。
给出所有 R 条双向路的描述,每条路连接了两个不同的草场,请计算最少的新建道路的数量,路径由若干道路首尾相连而成。
两条路径相互分离,是指两条路径没有一条重合的道路。
但是,两条分离的路径上可以有一些相同的草场。
对于同一对草场之间,可能已经有两条不同的道路,你也可以在它们之间再建一条道路,作为另一条不同的道路。
#include<bits/stdc++.h> using namespace std; const int N = 5010,M = 2e4+10; int h[M],e[M],idx,ne[M],n,m; int stk[N],dfn[N],low[N],scc_cnt,timestamp; bool is_stk[N],is_brige[M]; int id[N],top,d[N]; void add(int a,int b){ e[idx] = b,ne[idx] = h[a],h[a] = idx++; } void tarjan(int u,int fr){ dfn[u] = low[u] = ++timestamp; stk[++top] = u; for(int i = h[u];~i;i=ne[i]){ int j = e[i]; if(!dfn[j]){ tarjan(j,i); low[u] = min(low[u],low[j]); if(low[j]>low[u]){ is_brige[i] = true; is_brige[i^1] = true; } } else if(i != (fr^1)){ low[u] = min(low[u],dfn[j]); } } if(dfn[u] == low[u]){ int y; ++scc_cnt; do{ y = stk[top--]; id[y] = scc_cnt; }while(y!=u); } } int main(){ cin>>n>>m; memset(h,-1,sizeof h); for(int i = 1;i<=m;++i){ int a,b; cin>>a>>b; add(a,b); add(b,a); } tarjan(1,-1); for(int i = 0;i<idx;++i){ if(is_brige[i]){ d[id[e[i]]]++; } } int ans = 0; for(int i = 1;i<=scc_cnt;++i){ if(d[i]==1)++ans; } cout<<(ans+1)/2; return 0; }
2)割点
#include<bits/stdc++.h> using namespace std; const int N = 1e4+10; vector<int> G[N]; int low[N],dfn[N],ans; int n,m,root,timestamp; void tarjan(int u){ dfn[u] = low[u] = ++timestamp; int cnt = 0; for(auto to:G[u]){ if(!dfn[to]){ tarjan(to); low[u] = min(low[u],low[to]); if(low[to] >= dfn[u])++cnt; } else low[u] = min(low[u],dfn[to]); } if(u != root)++cnt; ans = max(ans,cnt); } int main(){ while(cin>>n>>m,n||m){ ans = 0; timestamp = 0; memset(dfn,0,sizeof dfn); for(int i = 0;i<n;++i)G[i].clear(); for(int i = 1;i<=m;++i){ int a,b; cin>>a>>b; G[a].push_back(b); G[b].push_back(a); } int cnt = 0; for(root = 0;root<n;++root){ if(!dfn[root]){ ++cnt; tarjan(root); } } cout<<ans+cnt-1<<'\n'; } }
3.二分图
给定一个 N 行 N 列的棋盘,已知某些格子禁止放置。
求最多能往棋盘上放多少块的长度为 2、宽度为 1 的骨牌,骨牌的边界与格线重合(骨牌占用两个格子),并且任意两张骨牌都不重叠。
#include<bits/stdc++.h> using namespace std; typedef pair<int,int> PII; #define x first #define y second const int N = 105; PII match[N][N]; bool g[N][N],st[N][N]; int n,m,dx[]={1,-1,0,0},dy[]={0,0,1,-1}; bool find(int x,int y){ for(int i = 0;i<4;++i){ int a=dx[i]+x,b=dy[i]+y; if(a<1||a>n||b<1||b>n||g[a][b]||st[a][b])continue; auto t = match[a][b]; st[a][b] = true; if(!t.x || find(t.x,t.y)){ match[a][b] = {x,y}; return true; } } return false; } int main(){ int res = 0; cin>>n>>m; for(int i = 1;i<=m;++i){ int a,b; cin>>a>>b; g[a][b] = true; } for(int i = 1;i<=n;++i){ for(int j = 1;j<=n;++j){ if((i+j)%2 && !g[i][j]){ if(find(i,j)){ memset(st,0,sizeof st); ++res; } } } } cout<<res<<'\n'; return 0; }
4.欧拉回路,路径
给定一张图,请你找出欧拉回路,即在图中找一个环使得每条边都在环上出现恰好一次。
第一行包含一个整数 t,t∈{1,2},如果 t=1,表示所给图为无向图,如果 t=2,表示所给图为有向图。
第二行包含两个整数 n,m,表示图的结点数和边数。
接下来 m 行中,第 i 行两个整数 vi,ui,表示第 i 条边(从 1 开始编号)。
如果 t=1 则表示 vi 到 ui 有一条无向边。
如果 t=2 则表示 vi 到 ui 有一条有向边。
图中可能有重边也可能有自环。
点的编号从 1 到 n。
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 10,M = 4e5 + 10; int din[N],dout[N],cnt,ans[M]; int type,n,m; int h[N],e[M],id[M],ne[M],idx,used[M]; void add(int a,int b,int i){ e[idx] = b,id[idx] = i,ne[idx] = h[a],h[a] = idx++; } void dfs(int u){ for(int &i = h[u];~i;){ int j = e[i]; if(used[i]){ i = ne[i]; continue; } if(type == 1){ used[i^1] = true; } int t = id[i]; i = ne[i]; dfs(j); ans[++cnt] = t; } } int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); memset(h,-1,sizeof h); cin>>type>>n>>m; for(int i = 1;i<=m;++i){ int a,b; cin>>a>>b; din[b]++; dout[a]++; add(a,b,i); if(type == 1) add(b,a,-i); } if(type == 1){ for(int i = 1;i<=n;++i){ if((din[i]+dout[i])&1){ cout<<"NO"<<'\n'; return 0; } } } else{ for(int i = 1;i<=n;++i){ if(din[i] != dout[i]){ cout<<"NO"<<'\n'; return 0; } } } for(int i = 1;i<=n;++i){ if(h[i] != -1){ dfs(i); break; } } if(cnt != m){ cout<<"NO"<<'\n'; } else{ cout<<"YES"<<'\n'; for(int i = cnt;i>=1;--i){ cout<<ans[i]<<" "; } } return 0; }
四、数据结构
1、主席树
—静态区间第 kk 小
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #include<bits/stdc++.h> using namespace std; const int N = 2e5 + 10; struct node{ int sum,l,r; }tree[N*60]; int root[N],a[N],sz = 0; vector<int> v; int get_id(int x){ return lower_bound(v.begin(),v.end(),x) - v.begin() + 1; } void update(int l,int r,int &x,int y,int pos){ x = ++sz; tree[x] = tree[y]; tree[x].sum++; if(l==r)return ; int m = (l+r)>>1; if(pos<=m)update(l,m,tree[x].l,tree[y].l,pos); else update(m+1,r,tree[x].r,tree[y].r,pos); } int query(int l,int r,int x,int y,int pos){ if(l==r)return l; int sum = tree[tree[y].l].sum - tree[tree[x].l].sum; int m = (l+r)>>1; if(sum>=pos)return query(l,m,tree[x].l,tree[y].l,pos); else return query(m+1,r,tree[x].r,tree[y].r,pos - sum); } int main(){ IOS; //freopen("a.txt","r",stdin); int n,m; cin>>n>>m; for(int i = 1 ;i<=n;++i){ cin>>a[i]; v.push_back(a[i]); } sort(v.begin(),v.end()); v.erase(unique(v.begin(),v.end()),v.end()); for(int i = 1;i<=n;++i){ update(1,n,root[i],root[i-1],get_id(a[i])); } for(int i = 1;i<=m;++i){ int x,y,k; cin>>x>>y>>k; cout<<v[query(1,n,root[x-1],root[y],k)-1]<<endl; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人