qduoj 142 ycb的ACM进阶之路 二进制优化多重背包

题目链接


关于多重背包以前做过几道水题,无非问法就是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;
}


你可能感兴趣的:(背包问题)