题目链接
关于多重背包以前做过几道水题,无非问法就是n个物品,每件物品有k件,价值v,重量w,给你一个容量为M的背包求最大价值之类的.
关于这类题目的话,01背包是所有背包问题的精髓,大部分问题都可以向01背包去转化,当然多重背包也不例外,时间复杂度比较高的一种做法是:将这n*k个物品全部看成每一个物品,这样就转化成了01背包,O(n*k*M)
还可以将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程: f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]} 复杂度同上
首先这个题一开始我认为是个01背包的,结果T了,后来发现T和V都<=10,对于1e5的数据量肯定有很多重复的,那么我们就可以统计出对应 t,v 的物品有多少件了,就转化成了多重背包。
但是对于今天咱们这个题,貌似这两种方法过不了的,所以我们就要来想想优化, 名字叫做二进制优化多重背包。
方法是:将第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 1 2 ....k件,如果我们将其拆分二进制的话恰好可以表示出0....k之间的每一个整数, n[i]-2^k+1 是为了凑剩下的
这样就将第i种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为O(V*Σlog n[i])的01背包问题,是很大的改进。
详细的请参考背包九讲
#include
using namespace std;
const int maxn=1e5+10;
int dp[maxn];
int book[20][20];
int T,n,m,t,v;
int main()
{
scanf("%d",&T);
while(T--)
{
memset(dp,0,sizeof(dp));
memset(book,0,sizeof(book));
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&t,&v);
book[t][v]++;
}
for(int i=1;i<=10;i++)
{
for(int j=1;j<=10;j++)
{
if(!book[i][j])
continue;
for(int k=1;k<=book[i][j];k*=2)
{
for(int w=m;w>=k*i;w--)
dp[w]=max(dp[w],dp[w-k*i]+k*j);
book[i][j]-=k;
}
int res=book[i][j];
if(res>0)
{
for(int w=m;w>=res*i;w--)
dp[w]=max(dp[w],dp[w-res*i]+res*j);
}
}
}
printf("%d\n",dp[m]);
}
return 0;
}