ACM板子——【动态规划问题】全收录
一、背包问题
【1.1】01背包

1 for(int i=1;i<=n;i++) 2 for(int j=m;j>=v[i];j--) 3 f[j]=max(f[j],f[j-v[i]]+w[i]);
【1.2】完全背包

1 #include<iostream>//用一维数据f[j]只记录一行数据,让j值顺序循环,顺序更新f[j]值 2 using namespace std; 3 const int N=1010; 4 int f[N],v[N],w[N]; 5 int main() 6 {//对于前i件物品,背包容量为j-w[i]时可能已经放入了第i件物品,容量为j时还可以再放入第i件物品, 7 int n,m;//所以用f[i][j-w[i]]更新f[i][j] 8 cin>>n>>m; 9 for(int i=1;i<=n;i++) 10 cin>>v[i]>>w[i]; 11 for(int i=1;i<=n;i++) 12 for(int j=v[i];j<=m;j++) 13 f[j]=max(f[j],f[j-v[i]]+w[i]); 14 cout<<f[m]<<endl; 15 return 0; 16 }
【1.3】多重背包问题1

1 #include<iostream>//多重背包可以转化为01背包 2 using namespace std;//01背包:第i种物品可以取0件、1件 3 const int N=110;//多重背包:第i种物品可以取0件、1件、2件、si件 4 int f[N];//求解:把第i种物品换成si件01背包种的物品,每件物品的体积为k*vi,价值为k*wi 5 int main() 6 { 7 int n,m; 8 cin>>n>>m; 9 for(int i=0;i<n;i++) 10 { 11 int v,w,s; 12 cin>>v>>w>>s; 13 for(int j=m;j>=v;j--) 14 for(int k=1;k<=s&&k*v<=j;k++) 15 f[j]=max(f[j],f[j-k*v]+k*w); 16 } 17 cout<<f[m]; 18 return 0; 19 }
【1.4】多重背包问题2(数据更大的情况下,二进制优化)

1 #include<iostream> 2 #include<algorithm> 3 #define endl '\n' 4 #define ll long long 5 using namespace std; 6 const int N=3e4+10; 7 int f[N],v[N],w[N]; 8 int main() 9 { 10 int n,m; 11 cin>>n>>m; 12 int cnt=0; 13 for(int i=1;i<=n;i++) 14 { 15 int a,b,s; 16 cin>>a>>b>>s; 17 int k=1; 18 while(k<=s) 19 { 20 cnt++; 21 v[cnt]=a*k; 22 w[cnt]=b*k; 23 s-=k; 24 k*=2; 25 } 26 if(s>0) 27 { 28 cnt++; 29 v[cnt]=a*s; 30 w[cnt]=b*s; 31 } 32 } 33 n=cnt; 34 for(int i=1;i<=n;i++) 35 for(int j=m;j>=v[i];j--) 36 f[j]=max(f[j],f[j-v[i]]+w[i]); 37 cout<<f[m]<<endl; 38 return 0; 39 }
(vector数组写法)

1 #include<iostream> 2 #include<vector> 3 using namespace std; 4 const int N=2010; 5 int f[N]; 6 struct Good{ 7 int v,w; 8 }; 9 int main() 10 { 11 int n,m; 12 cin>>n>>m; 13 vector<Good> goods; 14 for(int i=1;i<=n;i++) 15 { 16 int v,w,s; 17 cin>>v>>w>>s; 18 for(int k=1;k<=s;k*=2) 19 { 20 goods.push_back({v*k,w*k}); 21 s-=k; 22 } 23 if(s) 24 goods.push_back({v*s,w*s}); 25 } 26 for(auto good:goods) 27 for(int j=m;j>=good.v;j--) 28 f[j]=max(f[j],f[j-good.v]+good.w); 29 cout<<f[m]<<endl; 30 return 0; 31 }
【1.5】多重背包问题3(单调队列优化)
【1.6】混合背包问题
【1.7】二维费用背包问题

1 #include<iostream> 2 using namespace std; 3 const int N=110; 4 int f[N][N]; 5 int main() 6 { 7 int n,v,m; 8 cin>>n>>v>>m; 9 for(int i=0;i<n;i++) 10 { 11 int a,b,c; 12 cin>>a>>b>>c; 13 for(int j=v;j>=a;j--) 14 for(int k=m;k>=b;k--) 15 f[j][k]=max(f[j][k],f[j-a][k-b]+c); 16 } 17 cout<<f[v][m]; 18 return 0; 19 }
【1.8】分组背包问题

1 /* 2 f[j]=max(f[j],f[j-v[0]]+w[0],f[j-v[1]]+w[1],f[j-v[2]],w[2],...) 3 */ 4 #include<iostream> 5 using namespace std; 6 const int N=110; 7 int f[N][N]; 8 int main() 9 { 10 int n,v,m; 11 cin>>n>>v>>m; 12 for(int i=0;i<n;i++) 13 { 14 int a,b,c; 15 cin>>a>>b>>c; 16 for(int j=v;j>=a;j--) 17 for(int k=m;k>=b;k--) 18 f[j][k]=max(f[j][k],f[j-a][k-b]+c); 19 } 20 cout<<f[v][m]; 21 return 0; 22 }
二、线性DP
【2.1】数字金字塔

1 #include<iostream> 2 using namespace std; 3 const int N=510; 4 int f[N][N]; 5 int main() 6 { 7 int n; 8 cin>>n; 9 for(int i=1;i<=n;i++) 10 for(int j=1;j<=i;j++) 11 cin>>f[i][j]; 12 for(int i=n-1;i>=1;i--) 13 for(int j=1;j<=i;j++) 14 f[i][j]+=max(f[i+1][j],f[i+1][j+1]); 15 cout<<f[1][1]<<endl; 16 return 0; 17 }
【2.3】最长上升子序列

1 #include<iostream> 2 using namespace std; 3 const int N=1e5+10; 4 int a[N],f[N]; 5 int main() 6 { 7 int n; 8 cin>>n; 9 for(int i=1;i<=n;i++) cin>>a[i]; 10 for(int i=1;i<=n;i++) 11 { 12 f[i]=1; 13 for(int j=1;j<i;j++) 14 if(a[j]<a[i]) 15 f[i]=max(f[i],f[j]+1); 16 } 17 int ans=0; 18 for(int i=1;i<=n;i++) ans=max(ans,f[i]); 19 cout<<ans<<endl; 20 return 0; 21 }
【2.3.1】Array Division

1 int main() 2 { 3 cin.tie(0)->sync_with_stdio(0); 4 int t; 5 cin>>t; 6 while(t--) 7 { 8 int n; 9 cin>>n; 10 for(int i=1;i<=n;i++) 11 { 12 cin>>a[i]; 13 a[i]+=a[i-1]; 14 } 15 for(int i=1;i<=n;i++) 16 { 17 cin>>b[i]; 18 b[i]+=b[i-1]; 19 } 20 memset(f,-2,sizeof(f)); 21 f[0]=0; 22 for(int i=1;i<=n;i++) 23 for(int j=0;j<i;j++) 24 if(a[i]-a[j]>=b[i]-b[j]) 25 f[i]=max(f[i],f[j]+1); 26 if(f[n]<0)f[n]=-1; 27 cout<<f[n]<<endl; 28 } 29 return 0; 30 }
【2.4】最长上升子序列2(二分查找优化)

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=100010; 4 int n,a[N],q[N]; 5 int main()//时间复杂度NlogN 6 { 7 int n; 8 cin>>n; 9 for(int i=0;i<n;i++) cin>>a[i]; 10 int len=0;//存的是最大长度,一开始为0 11 q[0]=-2e9;//为了保证数组中,小于某个数的数一定存在 12 for(int i=0;i<n;i++) 13 { 14 int l=0,r=len; 15 while(l<r)//我们要找到小于a[i]的最大的数 16 { 17 int mid=l+r+1>>1;//因为我们算的是l=mid,所以要上取整 18 if(q[mid]<a[i]) l=mid;//所以我们要找到的答案一定在mid的右边 19 else r=mid-1; 20 } 21 len=max(len,r+1);//接完a[i]后长度会加1 22 q[r+1]=a[i];//q[r+1]一定大于等于a[i] 23 } 24 cout<<len; 25 return 0; 26 }
第二种写法

1 //如果我们把内层循环改为二分查找,就能把内层查找时间降为logn 2 //时间复杂度降为nlogn 3 //但是,二分查找的前提是有序序列,故增加一个b数组,用来记录上升子序列 4 #include<iostream> 5 using namespace std; 6 const int N=1e5+10; 7 int a[N],b[N],len; 8 int find(int x) 9 {//二分查找第一个大于等于x的位置 10 int l=1,r=len,mid; 11 while(l<=r) 12 { 13 mid=(l+r)/2; 14 if(x>b[mid])l=mid+1; 15 else r=mid-1; 16 } 17 return l; 18 } 19 int main() 20 { 21 int n; 22 cin>>n; 23 for(int i=1;i<=n;i++) 24 cin>>a[i]; 25 len=1; 26 b[1]=a[1]; 27 //动态更新b数组 28 for(int i=2;i<=n;i++) 29 { 30 if(a[i]>b[len])//大于则添加 31 b[++len]=a[i]; 32 else//小于等于则替换 33 { 34 int j=find(a[i]);//用a[i]替换掉b数据中第一个大于或者等于a[i]的元素 35 b[j]=a[i];//这样的上升子序列更有潜力 36 }//这样会使b的上升子序列的结尾元素更小,从而越有利于续接其它元素 37 } 38 cout<<len<<endl; 39 return 0; 40 }
【2.5】最长公共子序列

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1010; 4 char a[N],b[N]; 5 int f[N][N]; 6 int main() 7 { 8 int n,m; 9 cin>>n>>m>>a+1>>b+1; 10 for(int i=1;i<=n;i++) 11 { 12 for(int j=1;j<=m;j++) 13 { 14 f[i][j]=max(f[i-1][j],f[i][j-1]); 15 if(a[i]==b[j]) 16 f[i][j]=max(f[i][j],f[i-1][j-1]+1); 17 } 18 } 19 cout<<f[n][m]<<endl; 20 return 0; 21 }
【2.6】最短编辑距离

1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 const int N=1010; 5 char a[N],b[N]; 6 int n,m; 7 int f[N][N]; 8 int main() 9 { 10 cin>>n>>a+1>>m>>b+1; 11 for(int i=0;i<=n;i++) f[i][0]=i; 12 for(int i=0;i<=m;i++) f[0][i]=i; 13 for(int i=1;i<=n;i++) 14 for(int j=1;j<=m;j++) 15 { 16 f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1); 17 if(a[i]==b[j]) 18 f[i][j]=min(f[i-1][j-1],f[i][j]); 19 else 20 f[i][j]=min(f[i-1][j-1]+1,f[i][j]); 21 } 22 cout<<f[n][m]; 23 return 0; 24 }
【2.7】编辑距离

1 #include<iostream> 2 #include<algorithm> 3 #include<string.h> 4 using namespace std; 5 const int N=15,M=1010; 6 int n,m; 7 int f[N][N]; 8 char str[M][N]; 9 int edit_distance(char a[],char b[]) 10 { 11 int la=strlen(a+1),lb=strlen(b+1); 12 for(int i=0;i<=lb;i++)f[0][i]=i; 13 for(int i=0;i<=la;i++)f[i][0]=i; 14 for(int i=1;i<=la;i++) 15 for(int j=1;j<=lb;j++) 16 { 17 f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1); 18 f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j])); 19 } 20 return f[la][lb]; 21 } 22 int main() 23 { 24 cin>>n>>m; 25 for(int i=0;i<n;i++)cin>>str[i]+1; 26 while(m--) 27 { 28 char s[N]; 29 int limit; 30 cin>>s+1>>limit; 31 int res=0; 32 for(int i=0;i<n;i++) 33 if(edit_distance(str[i],s)<=limit) 34 res++; 35 cout<<res<<endl; 36 } 37 return 0; 38 }
三、区间DP
【3.1】石子合并

1 #include<iostream> 2 using namespace std; 3 const int N=310; 4 int n,s[N],f[N][N]; 5 int main() 6 { 7 cin>>n; 8 for(int i=1;i<=n;i++)cin>>s[i],s[i]+=s[i-1]; 9 for(int len=2;len<=n;len++)//区间长度为1不用枚举 10 for(int i=1;i+len-1<=n;i++) 11 { 12 int j=i+len-1; 13 f[i][j]=1e8; 14 for(int k=i;k<j;k++) 15 f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]); 16 } 17 cout<<f[1][n]<<endl; 18 }
【3.1.1】Doin's Time

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int mod=1000003; 4 const int N=310; 5 long long f[N][N],s[N],a[N][N]; 6 int main() 7 { 8 int n; 9 cin>>n; 10 for(int i=1;i<=n;i++) 11 cin>>s[i]; 12 for(int i=1;i<=n;i++) 13 { 14 long long now=1; 15 for(int j=i;j<=n;j++) 16 { 17 now=now*s[j]%mod; 18 a[i][j]=now; 19 } 20 } 21 for(int len=2;len<=n;len++) 22 for(int i=1;i+len-1<=n;i++) 23 { 24 int j=i+len-1; 25 for(int k=i;k<j;k++) 26 { 27 f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+(a[i][k]-a[k+1][j])*(a[i][k]-a[k+1][j])); 28 } 29 } 30 cout<<f[1][n]<<endl; 31 }
四、计数类DP
【4.1】整数划分

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1010,mod=1e9+7; 4 int n,f[N];//每种物品可以用无限次,完全背包问题 5 int main() 6 { 7 8 /* 9 f[i][j] = f[i - 1][j] + f[i - 1][j - i] + f[i - 1][j - 2 * i] + ...; 10 f[i][j - i] = f[i - 1][j - i] + f[i - 1][j - 2 * i] + ...; 11 因此 f[i][j]=f[i−1][j]+f[i][j−i];f[i][j]=f[i−1][j]+f[i][j−i]; (这一步类似完全背包的推导 12 */ 13 cin>>n; 14 f[0]=1; 15 for(int i=1;i<=n;i++) 16 for(int j=i;j<=n;j++) 17 f[j]=(f[j]+f[j-i])%mod; 18 cout<<f[n]<<endl; 19 return 0; 20 }
五、数位统计DP
【5.1】计数问题
六、状态压缩DP
【6.1】蒙德里安的梦想

1 /*我们考虑按列摆放,某列的各行用0或1表示摆放状态 2 如果某行是1,表示横放,并且向下一列伸出 3 如果某行是0,表示竖放,或者由前一列伸出 4 状态表示:f[i][j]表示摆放第i列,状态为j时的方案数 5 状态转移:f[i-1][k]->f[i][j] 6 答案:f[m][0]表示已经摆完了m列,这一列不向下一列伸出 7 */ 8 #include<bits/stdc++.h> 9 using namespace std; 10 int f[12][1<<11]; 11 bool st[1<<11]; 12 int main() 13 { 14 int m,n; 15 while(cin>>n>>m,m||n) 16 { 17 memset(f,0,sizeof f); 18 f[0][0]=1;//f[0][0]=1,表示第0列不放矩形,是合法的状态,所以是1,其它为0 19 for(int i=0;i<1<<n;i++)//c从0枚举到2的n次方-1 20 { 21 st[i]=true;//先假设i是真的 22 int cnt=0;//记录合并列中连续0的个数 23 for(int j=0;j<n;j++) 24 { 25 if(i>>j&1)//如果是1 26 { 27 if(cnt&1)//如果连续0的个数是奇数 28 { 29 st[i]=false;//记录i不合法 30 break; 31 } 32 } 33 else cnt++;//如果是0,记录0的个数 34 } 35 if(cnt&1)st[i]=false;//处理高位0的个数 36 } 37 for(int i=1;i<=m;i++)//阶段:枚举第i列的状态 38 for(int j=0;j<1<<n;j++)//状态:枚举第i列的状态j 39 for(int k=0;k<1<<n;k++)//状态:枚举第i-1列的状态k 40 //两列状态兼容,不出现重叠1,不出现连续的奇数个0 41 if((j&k)==0&&st[j|k]) 42 f[i][j]+=f[i-1][k];//前一列兼容状态的方案数之和 43 cout<<f[m][0]<<endl; 44 } 45 /* 46 假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示: 47 A = 0011 1100 48 B = 0000 1101 49 ----------------- 50 A&B = 0000 1100 51 A|B = 0011 1101 52 A^B = 0011 0001 53 ~A = 1100 0011 54 */ 55 return 0; 56 }
最短Hamilton路径
七、树形DP
【7.1】没有上司的舞会

1 #include <iostream> 2 #include <algorithm> 3 #include <cstring> 4 using namespace std; 5 const int N = 6010; 6 int n; 7 int happy[N]; //每个职工的高兴度 8 int f[N][2]; 9 int e[N],ne[N],h[N],idx; //链表,用来模拟建一个树 10 bool has_father[N]; //判断当前节点是否有父节点 11 void add(int a,int b){ //把a插入树中 12 e[idx] = b,ne[idx] = h[a],h[a] = idx ++; 13 } 14 void dfs(int u){ //开始求解题目 15 f[u][1] = happy[u]; //如果选当前节点u,就可以把f[u,1]先怼上他的高兴度 16 for(int i = h[u];i!=-1;i = ne[i]){ //遍历树 17 int j = e[i]; 18 dfs(j); //回溯 19 //状态转移部分,上面有详细讲解~ 20 f[u][0] += max(f[j][1],f[j][0]); 21 f[u][1] += f[j][0]; 22 } 23 } 24 int main(){ 25 scanf("%d",&n); 26 for(int i = 1;i <= n;i ++) scanf("%d",&happy[i]); //输入每个人的高兴度 27 memset(h,-1,sizeof h); //把h都赋值为-1 28 for(int i = 1;i < n;i ++){ 29 int a,b; //对应题目中的L,K,表示b是a的上司 30 scanf("%d%d",&a,&b); 31 has_father[a] = true; //说明a他有爸爸(划掉)上司 32 add(b,a); //把a加入到b的后面 33 } 34 int root = 1; //用来找根节点 35 while(has_father[root]) root ++; //找根节点 36 dfs(root); //从根节点开始搜索 37 printf("%d\n",max(f[root][0],f[root][1])); //输出不选根节点与选根节点的最大值 38 return 0; 39 }
八、记忆搜索
【8.1】滑雪

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=310; 4 int n,m,h[N][N],f[N][N]; 5 int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; 6 int dp(int x,int y) 7 { 8 int &v=f[x][y]; 9 if(v!=-1) return v; 10 11 v=1; 12 for(int i=0;i<4;i++) 13 { 14 int a=x+dx[i],b=y+dy[i]; 15 if(a>=1&&a<=n&&b>=1&&b<=m&&h[a][b]<h[x][y]) 16 v=max(v,dp(a,b)+1); 17 } 18 return v; 19 } 20 int main() 21 { 22 cin>>n>>m; 23 for(int i=1;i<=n;i++) 24 for(int j=1;j<=m;j++) 25 cin>>h[i][j]; 26 memset(f,-1,sizeof f); 27 int res=0; 28 for(int i=1;i<=n;i++) 29 for(int j=1;j<=m;j++) 30 res=max(res,dp(i,j)); 31 cout<<res<<endl; 32 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现