背包问题,最经典的dp
首先动态规划问题,我们需要考虑的有两点,1是状态表示,2是状态计算。
状态表示f[i,j]:首先是一个集合,只考虑前i个物品,且总体积不大于j的所有选法,然后集合的属性这里以最大值表示
状态计算:集合的划分,也就是状态转移方程的求解
01背包
顾名思义,01的意思是指每个物品可以选择的次数为0或者1,选到第i个物品的时候,就可以往前推选择第i-1个物品的情况,如果第i个物品没有选,那么选择第i-1个物品的时候体积也是j,也就是f[i][j]=f[i-1][j],如果第i个物品选了,那么第i-1个物品的时候体积就是j-v[i],即f[i][j]=f[i-1][j-v[i]],需要求的是最大值,只需要在这两者间取最大的一个即可
朴素的代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int v[N],w[N];
int dp[N][N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>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]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
else
dp[i][j]=dp[i-1][j];
}
}
cout<<dp[n][m]<<endl;
return 0;
}
滚动数组优化
可以看出当选择到第i个物品时,只与选第i-1个时有关
那么是否可以直接删除掉第一维呢
删除后的转移方程为:dp[j]=max(dp[j],dp[j-v[i]]+w[i])显然是不对的,
从二维的可以看出来,第i-1个物品选择时的体积一定小于等于第i次选择的,
如果我们写成一维的情况仍然按照体积从小到大的话,前边的数据会提前发生改变,影响后边的数据
除此之外,当j小于v[i]时,dp[j]=dp[j]很明显是一句废话(
所以应该修改为
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]);
}
}
完全背包
完全的意思就是指每个物品可以选择的次数是无数次
当选择到第i个物品时,i可以选择的次数很多种,假设为k,那么选择第i-1个物品时候的体积就是j-kv[i],方程为dp[i][j]=dp[i-1][j-kv[i]]+k*w[i]
朴素代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int v[N], w[N];
int dp[N][N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> 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]);
}
}
cout<<dp[n][m]<<endl;
return 0;
}
时间复杂度达到了1e9,果不其然TLE,接下来就是考虑如何优化
dp[i,j]=max(dp[i-1,j],dp[i-1,j-v[i]]+w[i],dp[i-1,j-2*v[i]]+2*w[i]........)
dp[i,j-v[i]]=max( dp[i-1,j-v[i]] dp[i-1,j-2*v[i]+w[i]]..........)
dp[i,j-2v[i]]=max( dp[i-1,j-2*v[i]],dp[i-1,j-3v[i]]+w[i]
....
...
...
...
依次类推,每一项的最大值都是dp[i-1,j]与其后边一项的最大值加w[i]比较取最大值
然后得到以下优化,仍然是使用滚动数组
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int v[N], w[N];
int dp[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> 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]);
cout<<dp[m]<<endl;
return 0;
}
多重背包
与完全背包的区别在于,每个物品有一定的数量
朴素代码
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int v[N],w[N],s[N];
int dp[N][N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>v[i]>>w[i]>>s[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k);
}
}
cout<<dp[n][m]<<endl;
return 0;
}
优化为01背包
复杂度由On3变为On2logn
二进制下的转化,某个物品有m个,那么m可以进行一下拆分
1 2 4 8 …即把m拆开,拆成这些数字相加,使这些数字相加得到的结果是m
可以证1-m之间的数字均可以由这些数字组成,(此处不做证明
所以优化之后变成了01背包
#include<bits/stdc++.h>
using namespace std;
const int N=25000;
int v[N],w[N];
int dp[N];
int n,m;
int a,b,c; //a是体积,b是价值,c是个数
int main()
{
cin>>n>>m;
int cnt=1;
for(int i=0;i<n;i++)
{
cin>>a>>b>>c;
int res=1;
while(res<=c)
{
v[cnt]=res*a;
w[cnt]=res*b;
cnt++;
c-=res;
res*=2;
}
if(c!=0)
{
v[cnt]=c*a;
w[cnt]=c*b;
cnt++;
}
}
for(int i=1;i<cnt;i++)
{
for(int j=m;j>=v[i];j--)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
cout<<dp[m]<<endl;
return 0;
}
单调队列进行优化的还没看,之后补上(
分组背包
分组背包感觉写起来和多重背包有一点点像,在某一组中,可以选择的情况有不选,从第1到s[i],都有可能
代码
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int s[N];
int v[N][N];
int w[N][N];
int dp[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s[i];
for(int j=1;j<=s[i];j++)
cin>>v[i][j]>>w[i][j];
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
dp[i][j]=dp[i-1][j];
for(int k=1;k<=s[i];k++)
{
if(j>=v[i][k])
dp[i][j]=max(dp[i][j],dp[i-1][j-v[i][k]]+w[i][k]);
}
}
}
cout<<dp[n][m]<<endl;
return 0;
}
(今日分享就到这里,剩下的之后补齐
制作不易,感谢观看