赞
踩
原文链接:https://woozie.blog/index.php/2022/10/09/26/
目录
dp[i][j]的含义:前i个物品,容量为j的情况下能装物品的价值最大值
第一层循环——从1到n个物品
第二层循环——从容量0到m
对于第i个物品,当容量为j时
1. v[i]>j 即物品大于容量,不能放,则继承 dp[i][j]=dp[i-1][j];
2. v[i]<=j 选择最大价值 max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]) //dp[i-1][j]为不放直接继承,dp[i-1][j-v[i]]+w[i]为放第i件物品
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1010;
- int n,m; //n物品个数,m背包容量
- int v[N],w[N]; //v物品所占空间,w物品价值
- int dp[N][N];
- int main()
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++)
- scanf("%d%d",&v[i],&w[i]);
- for(int i=1;i<=n;i++)
- for(int j=0;j<=m;j++)
- if(j<v[i])
- dp[i][j]=dp[i-1][j]; //继承
- else
- dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]); //更新
- printf("%d",dp[n][m]);
- }
dp[i][j]能求得任意i,j情况下的最优解,但由于最终只需要求dp[n][m],所以只需要一维就可以维持
枚举背包容量需要逆序!
简单来说因为正序会使后面要用到的数据被覆盖,而倒序不会出现这个问题。
具体来讲我也忘了
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1010;
- int n,m;
- int v[N],w[N];
- int dp[N];
- int main()
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++)
- scanf("%d%d",&v[i],&w[i]);
- for(int i=1;i<=n;i++)
- for(int j=m;j>=v[i];j--)
- dp[j]=max(dp[j],dp[j-v[i]]+w[i]); //倒序,防止覆盖
- printf("%d",dp[m]);
- }
第一层循环——枚举物品
第二层循环——枚举容量
第三层循环——枚举个数 条件为k*v[i]<=j,超过容量即退出
递推式 dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]) 前者表示维持当前最大值,后者表示取k个i物品
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1010;
- int n,m;
- int v[N],w[N];
- int dp[N][N];
- int main()
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++)
- scanf("%d%d",&v[i],&w[i]);
- for(int i=1;i<=n;i++)
- for(int j=0;j<=m;j++)
- for(int k=0;k*v[i]<=j;k++)
- dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
- printf("%d",dp[n][m]);
- }
优化思路
- f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
- f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
- 由上两式,可得出如下递推关系:
- f[i][j]=max(f[i,j-v]+w , f[i-1][j])
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1010;
- int n,m;
- int v[N],w[N];
- int dp[N][N];
- int main()
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++)
- scanf("%d%d",&v[i],&w[i]);
- for(int i=1;i<=n;i++)
- {
- for(int j=0;j<=m;j++) //从小到大枚举,与01背包不同
- {
- dp[i][j]=dp[i-1][j];
- if(j>=v[i])
- dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
- }
- }
- printf("%d",dp[n][m]);
- }
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1010;
- int n,m;
- int v[N],w[N];
- int dp[N];
- int main()
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++)
- scanf("%d%d",&v[i],&w[i]);
- for(int i=1;i<=n;i++)
- for(int j=v[i];j<=m;j++)
- dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
- printf("%d",dp[m]);
- }
01背包是特殊的多重背包
代码与01背包雷同
- #include<bits/stdc++.h>
- using namespace std;
- const int N=110;
- int n,m;
- int v[N],w[N],s[N];
- int dp[N];
- int main()
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++)
- scanf("%d%d%d",&v[i],&w[i],&s[i]);
- for(int i=1;i<=n;i++)
- for(int j=m;j>=v[i];j--)
- for(int k=0;k<=s[i]&&k*v[i]<=j;k++) //01背包的k相当于恒为1
- dp[j]=max(dp[j-k*v[i]]+k*w[i],dp[j]);
- printf("%d",dp[m]);
- }
求区间最优解,将区间分隔为更小的区间,再由小区间最优解得到大区间最优解
模板
- for(int len=1;len<=n;len++) //先枚举长度
- {
- for(int i=1;i+len-1<=n;i++) //枚举起点
- {
- int j=i+len-1; //由起点和长度得出终点
- for(int k=i;k+1<=j;k++)
- dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+______);
- }
- }
石子合并
- #include<bits/stdc++.h>
- using namespace std;
- const int N=310;
- int a[N],b[N],dp[N][N];
- int n;
- int main()
- {
- scanf("%d",&n);
- for(int i=1;i<=n;i++)
- {
- scanf("%d",&a[i]);
- b[i]=b[i-1]+a[i];//记录前缀和
- }
- memset(dp,0x3f3f3f3f,sizeof(dp));//初始化
- for(int len=1;len<=n;len++)
- {
- for(int i=1;i+len-1<=n;i++)
- {
- int j=len+i-1;
- if(len==1)
- {
- dp[i][j]=0;//最小区间
- continue;
- }
- for(int k=i;k<=j-1;k++)
- dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+b[j]-b[i-1]);
- }
- }
- printf("%d",dp[1][n]);
- }
思路:将链打开
只需要将前n-1个数复制到后面
如123456 -> 12345612345
最后求max(dp(1,n),dp(2,n+1),......,dp(n,2n-1))
题目:环形石子合并
- #include<bits/stdc++.h>
- using namespace std;
- const int N=210;
- int n;
- int dpmax[2*N][2*N],dpmin[2*N][2*N],a[2*N];
- int b[2*N];
- int main()
- {
- scanf("%d",&n);
- for(int i=1;i<=n;i++)
- {
- scanf("%d",&a[i]);
- a[i+n]=a[i];
- }
- for(int i=1;i<=2*n;i++)
- b[i]=b[i-1]+a[i];
- memset(dpmin,0x3f3f3f,sizeof(dpmin));
- memset(dpmax,0,sizeof(dpmax)); //注意预处理!!!!
-
- for(int len=1;len<=n;len++)
- {
- for(int i=1;i+len-1<=2*n;i++)
- {
- int j=i+len-1;
- if(len==1)
- {
- dpmax[i][i]=0;
- dpmin[i][i]=0;
- continue;
- }
- for(int k=i;k+1<=j;k++)
- {
- dpmin[i][j]=min(dpmin[i][j],dpmin[i][k]+dpmin[k+1][j]+b[j]-b[i-1]);
- dpmax[i][j]=max(dpmax[i][j],dpmax[i][k]+dpmax[k+1][j]+b[j]-b[i-1]);
- }
- }
- }
- int maxn=0,minn=0x3f3f3f;
- for(int i=1;i<=n;i++)
- maxn=max(dpmax[i][i+n-1],maxn),minn=min(dpmin[i][i+n-1],minn);
- printf("%d\n%d",minn,maxn);
- }
-
AcWing 900. 整数划分
题意:一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中 n1≥n2≥…≥nk,k≥1。有多少种表示方法。
可转化为完全背包
有容量为1~n的n种物品,使背包容量恰好为n,问有多少种放法
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1010;
- const int MOD=1e9+7;
- int n;
- int dp[N];
- int main()
- {
- scanf("%d",&n);
- dp[0]=1; //0有一种放法,后面的状态全由0转移出来
- for(int i=1;i<=n;i++)
- for(int j=i;j<=n;j++)
- {
- dp[j]+=dp[j-i];
- dp[j]%=MOD;
- }
- printf("%d",dp[n]);
- }
Acwing 1021. 货币系统
给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。
输入格式
第一行,包含两个整数n和m。
接下来n行,每行包含一个整数,表示一种货币的面值。
输出格式
共一行,包含一个整数,表示方案数。
数据范围
n≤15,m≤3000
输入样例:
- 3 10
- 1
- 2
- 5
输出样例:
10
- #include<bits/stdc++.h>
- #define ll long long
- using namespace std;
- const int N=20;
- const int M=3010;
- ll n,m;
- ll dp[M],a[N];
- int main()
- {
- scanf("%lld%lld",&n,&m);
- for(int i=1;i<=n;i++)
- scanf("%lld",&a[i]);
- dp[0]=1;
- for(int i=1;i<=n;i++)
- for(int j=a[i];j<=m;j++)
- dp[j]+=dp[j-a[i]];
- printf("%lld",dp[m]);
- }
最长上升子序列
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1010;
- int n;
- int a[N],dp[N];
- int main()
- {
- scanf("%d",&n);
- for(int i=1;i<=n;i++)
- scanf("%d",&a[i]);
- for(int i=1;i<=n;i++)
- {
- dp[i]=1;
- for(int j=1;j<=i;j++)
- if(a[j]<a[i])
- dp[i]=max(dp[i],dp[j]+1);
- }
- int res=0;
- for(int i=1;i<=n;i++)
- res=max(res,dp[i]);
- printf("%d",res);
- }
最短编辑距离
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1010;
- int n,m;
- string a,b;
- int dp[N][N];
- int main()
- {
- scanf("%d",&n);
- cin>>a;
- scanf("%d",&m);
- cin>>b;
- memset(dp,0x3f3f3f3f,sizeof(dp)); //求最小值要初始化!!!!
- for(int i=1;i<=n;i++) //初始化
- dp[i][0]=i;
- for(int i=0;i<=m;i++) //初始化
- dp[0][i]=i;
- for(int i=0;i<n;i++)
- for(int j=0;j<m;j++)
- {
- int x=i+1,y=j+1;
- dp[x][y]=min(dp[x-1][y-1],min(dp[x-1][y],dp[x][y-1]))+1;
- if(a[i]==b[j])
- dp[x][y]=min(dp[x][y],dp[x-1][y-1]);
- }
- printf("%d",dp[n][m]);
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。