动态规划二
复健\(Day4\)
动态规划(二)背包\(DP\)
\(1.01\)背包
每个物品只有一件
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1010;
int dp[N],v[N],w[N];
int main()
{
int n,V;
cin>>n>>V;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++)
{
for(int j=V;j>=v[i];j--) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
printf("%d\n",dp[V]);
return 0;
}
\(2.\)完全背包
每个物品有无穷件
#include<iostream>
#include<cstdio>
#define maxn 1010
using namespace std;
int f[maxn],n,V;
int v[maxn],w[maxn];
int main()
{
cin>>n>>V;
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<=V;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);
}
printf("%d\n",f[V]);
return 0;
}
\(3.\)多重背包
每个物品有有限件
二进制优化代码,时间复杂度为\(O(V\sum logs[i])\)(朴素算法是\(O(V\sum s[i])\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 2010
using namespace std;
int dp[maxn],v[maxn],w[maxn],s[maxn],nv[maxn],nw[maxn],cnt;
int main()
{
int n,V;
cin>>n>>V;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i]>>s[i];
int k;
for(k=1;s[i]-(1<<k)+1>0;k++)
{
nv[++cnt]=(1<<(k-1))*v[i];
nw[cnt]=(1<<(k-1))*w[i];
}
k--;
nv[++cnt]=(s[i]-(1<<k)+1)*v[i];
nw[cnt]=(s[i]-(1<<k)+1)*w[i];
}
for(int i=1;i<=cnt;i++)
{
for(int j=V;j>=nv[i];j--) dp[j]=max(dp[j],dp[j-nv[i]]+nw[i]);
}
printf("%d\n",dp[V]);
return 0;
}
单调队列优化代码,时间复杂度为\(O(nV)\)
\(f\)数组是按类更新的,就将其按照体积\(v[i]\)的余数拆分为\(v\)个类
同时,\(f[j]\)是由前面不超过数量\(s[j]\)的同类只递推得到,故我们还可以通过单调队列来维护窗口最大值
因为我们这种更新时是需要顺序更新的,二我们普通的算法中,\(f\)时逆序更新的,那么我们可以备份一个\(g\)数组,用来更新即可
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 20010
using namespace std;
int f[maxn],g[maxn],q[maxn];
int n,V;
int v,w,s;
int main()
{
cin>>n>>V;
for(int i=1;i<=n;i++)
{
cin>>v>>w>>s;
memcpy(g,f,sizeof(f));
for(int j=0;j<v;j++)//j是余数
{
int h=1,t=0;
for(int k=j;k<=V;k+=v)//对每个类使用单调队列
{
if(h<=t&&q[h]<k-s*v) h++;//q[h]不在窗口[k-s*v,k-v]内,出队
if(h<=t) f[k]=max(g[k],g[q[h]]+(k-q[h])/v*w);
while(h<=t&&g[k]>=g[q[t]]+(k-q[t])/v*w) t--;
q[++t]=k;
}
}
}
printf("%d\n",f[V]);
return 0;
}
\(4.\)混合背包
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 10010
using namespace std;
int f[maxn],nv[maxn],nw[maxn],ns[maxn];
int cnt;
int n,V;
int main()
{
cin>>n>>V;
int v,w,s;
for(int i=1;i<=n;i++)
{
cin>>v>>w>>s;
if(s==-1)
{
nv[++cnt]=v;
nw[cnt]=w;
ns[cnt]=-1;
}
else if(s==0)
{
nv[++cnt]=v;
nw[cnt]=w;
ns[cnt]=0;
}
else
{
int k;
for(k=1;s-(1<<k)+1>0;k++)
{
nv[++cnt]=(1<<(k-1))*v;
nw[cnt]=(1<<(k-1))*w;
ns[cnt]=-1;
}
k--;
nv[++cnt]=(s-(1<<k)+1)*v;
nw[cnt]=(s-(1<<k)+1)*w;
ns[cnt]=-1;
}
}
for(int i=1;i<=cnt;i++)
{
if(ns[i]==-1)
{
for(int j=V;j>=nv[i];j--) f[j]=max(f[j],f[j-nv[i]]+nw[i]);
}
else
{
for(int j=nv[i];j<=V;j++) f[j]=max(f[j],f[j-nv[i]]+nw[i]);
}
}
printf("%d\n",f[V]);
return 0;
}
但是这里要特别注意一个点,就是我们数组的大小要开大一点,因为我们对多重背包采用了二进制优化,会使数量变多
\(5.\)二维费用背包
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 10010
using namespace std;
int f[maxn][maxn];
int cnt;
int n,V,M;
int main()
{
cin>>n>>V>>M;
int v,w,m;
for(int i=1;i<=n;i++)
{
cin>>v>>m>>w;
for(int j=V;j>=v;j--)
{
for(int k=M;k>=m;k--) f[j][k]=max(f[j][k],f[j-v][k-m]+w);
}
}
printf("%d\n",f[V][M]);
return 0;
}
\(6.\)分组背包
\(f[i][j]\)表示前\(i\)组背包容量为\(j\)的最优值
但是我们可以优化空间复杂度
必须先枚举体积,再枚举决策
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 1010
using namespace std;
int f[maxn];
int s;
int v[maxn],w[maxn];
int n,V;
int main()
{
cin>>n>>V;
for(int i=1;i<=n;i++)
{
cin>>s;
for(int j=1;j<=s;j++) cin>>v[j]>>w[j];
for(int j=V;j>=1;j--)
{
for(int k=0;k<=s;k++)//可以不选,所以从0开始
{
if(j>=v[k]) f[j]=max(f[j],f[j-v[k]]+w[k]);
}
}
}
printf("%d\n",f[V]);
return 0;
}
\(7.\)求方案数
\(f[i]\)表示背包容量为\(i\)时能装入的最大价值(同上),\(c[i]\)表示背包容量为\(i\)时的最优选法方案数
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 1010
using namespace std;
const int mod=1e9+7;
int f[maxn],c[maxn];
int main()
{
int n,V;
cin>>n>>V;
for(int i=0;i<=V;i++) c[i]=1;//未装入物品时f[i]=0,c[i]=1(因为不装物品也是一种方案)
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=V;j>=v;j--)
{
if(f[j-v]+w>f[j])
{
f[j]=f[j-v]+w;
c[j]=c[j-v];
}
else if(f[j-v]+w==f[j])
{
c[j]=(c[j]+c[j-v])%mod;
}
}
}
printf("%d\n",c[V]);
return 0;
}
\(8.\)求具体方案
\(01\)背包的具体方案:这里设\(f[i][j]\)表示从第\(i\)个物品到最后容量为\(j\)的最优值,而\(01\)背包中,\(f[i][j]\)表示从第一个物品到第\(i\)个的最优值
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 1010
using namespace std;
int f[maxn][maxn];
int v[maxn],w[maxn];
int main()
{
int n,V;
cin>>n>>V;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=n;i>=1;i--)
{
for(int j=0;j<=V;j++)
{
f[i][j]=f[i+1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
}
}
int j=V;
for(int i=1;i<=n;i++)
{
if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i])
{
printf("%d ",i);
j-=v[i];
}
}
return 0;
}
完全背包的具体方案
将\(21\)行和\(27\)行两个\(i+1\)处改为\(i\)即可,因为我们的完全背包可以从同一行更新得到
正因无所有,才会无所畏