接下来是多重背包(MultiplePack),
问题:有N种物品和一个容量为V的背包,第i件物品最多有n[i]件可用,每件的费用是w[i],价值是v[i]。
将物品放入背包中,使得总容量不超过V,而且总价值最大。
因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。
令dp[i][w]表示前i种物品恰放入一个容量为w的背包的最大权值,
则有状态转移方程:
dp[i][w]=max{dp[i-1][w-k*w[i]]+k*v[i]|0<=k<=n[i]}
代码实现:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=300+10;
const int MAXW=300+10;
int dp[MAXW];
int n[MAXN],v[MAXN],w[MAXN];
int main()
{
//freopen("in.txt","r",stdin);
int N,W;
while(scanf("%d %d",&N,&W)!=EOF)
{
for(int i=0;i<N;i++)
scanf("%d %d %d",&v[i],&w[i],&n[i]);
memset(dp,0,sizeof(dp));
for(int i=0;i<N;i++)
{
for(int j=0;j<n[i];j++)
{
for(int k=W;k>=w[i];k--)
{
dp[k]=max(dp[k],dp[k-w[i]] + v[i]);
}
}
}
printf("%d\n",dp[W]);
}
return 0;
}
题意:多重背包价格和重量交换了,要获取最大的重量。
代码实现:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=300+10;
const int MAXW=300+10;
int dp[MAXW];
int n[MAXN],v[MAXN],w[MAXN];
int main()
{
//freopen("in.txt","r",stdin);
int N,V;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&V,&N);
for(int i=0;i<N;i++)
scanf("%d %d %d",&v[i],&w[i],&n[i]);
memset(dp,0,sizeof(dp));
for(int i=0;i<N;i++)
{
for(int j=0;j<n[i];j++)
{
for(int k=V;k>=v[i];k--)
{
dp[k]=max(dp[k],dp[k-v[i]] + w[i]);
}
}
}
printf("%d\n",dp[V]);
}
return 0;
}
其实可以将多重背包转化为0-1背包。
例如第i件物品有num个,将其变成num个属性(价格,重量)跟其一样的物品就行。然后用0-1背包的思想去做。
时间复杂度是O(W*(num1+num2+....num(n))).
用hdu2191的代码实现:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=300000+10;
int dp[MAXN];
int num,v[MAXN],w[MAXN];
int main()
{
//freopen("in.txt","r",stdin);
int N,V,vv,ww;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&V,&N);
int cnt=0;
for(int i=0;i<N;i++)
{
scanf("%d %d %d",&vv,&ww,&num);
//分成num个物品
for(int j=0;j<num;j++)
{
v[cnt]=vv;
w[cnt]=ww;
cnt++;
}
}
memset(dp,0,sizeof(dp));
//0-1背包
for(int i=0;i<cnt;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;
}
多重背包二进制拆分
方法是将物品i分成若干件,其中每一件物品都有一个系数,这件物品的费用和价值都是原来的费用和价值乘以这个系数,
使得这些系数分别为1,2,4,…,2^(k-1),n[i]-2^k+1,且k满足n[i]-2^k+1>0的最大整数。
例如,n[i]=13,就将该物品拆成系数为1、2、4、6的四件物品。分成的这几件物品的系数和为n[i],
表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示。
时间复杂度是(w*log(num1+log(num2)+....log(num(n))))
还是用hdu2191的代码去实现:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=300000+10;
int dp[MAXN];
int num,v[MAXN],w[MAXN];
int main()
{
//freopen("in.txt","r",stdin);
int N,V,vv,ww;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&V,&N);
int cnt=0;
for(int i=0;i<N;i++)
{
scanf("%d %d %d",&vv,&ww,&num);
//二进制分解num个物品
for(int j=1;j<=num;j*=2)
{
v[cnt]=vv*j;
w[cnt]=ww*j;
cnt++;
num-=j;
}
if(num>0)
{
w[cnt]=num*ww;
v[cnt++]=num*vv;
}
}
memset(dp,0,sizeof(dp));
//0-1背包
for(int i=0;i<cnt;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;
}
据说还有用单调队列去实现多重背包,时间复杂度是O(W*N),还没有去了解。等学了再加入到这里。
推荐题目:POJ1276