欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-17 16:52:46
...

题目链接


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