bzoj1190 [HNOI2007]梦幻岛宝珠 ( 二进制分组优化背包DP)

53 篇文章 0 订阅
51 篇文章 1 订阅

bzoj1190 [HNOI2007]梦幻岛宝珠

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=1190

题意:
给你N颗宝石,每颗宝石都有重量和价值。要你从这些宝石中选取一些宝石,保证总重量不超过W,并输出最大的总价值。数据范围:N<=100;W<=2^30,并且保证每颗宝石的重量符合a*2^b(a<=10;b<=30)

数据范围
N<=100;W<=2^30,并且保证每颗宝石的重量符合a*2^b(a<=10,b<=30)
对于每颗宝石 1≤weighti≤2^30,0≤valuei≤2^30

题解:
01背包当然TLE及MLE。
既然都给出来每颗宝石的重量符合a*2^b了,那必然是要二进制优化。
W的值非常大,想到按位来DP W。

正解的状态定义太巧了。
f[i][j]表示容量为 j*2^i+(w&((1<< i)-1)) 的最大价值。
(例如,对W=10000101010来说 f[5][3]代表的容积是11101010,实际上就是把之前位一起算上)
定义top为 W最高位,那么答案就在f[top][1]中。

初始时,先对b值相同的做一次01背包,答案放在f[a][0~b]中。这是单用这一层的物品所能组合出的最大价值。这时候f[i][j]代表的空间仅仅是 j*2^i。

接下来考虑怎么把更小层的填进来。
层与层之间转移方程为:
for i=1~top
for j=1000~0
for k=0~j
f[i][j]=max(f[i][j],f[i][j-k]+f[i-1][2*k+((w>>(i-1) )&1)])
由于j是倒序枚举的,在更新f[i][j]时,f[i][j-k]还只有i层的物品,这其实是用f[i][j]按照用f[i][j-k]代表的空间( (j-k)*2^i )选第i层的物品,然后用剩下的空间装0~i-1层物品的最大价值。
这个2*k+((w>>(i-1) )&1) 就是剩下的空间。

(我们更新后的f[i][j]代表 j*2^i+(w&((1<< i)-1))的空间,现在第i层的用了 (j-k) * 2^i 的空间,减一下,用x * 2^(i-1)表示,即是x=2 * k+((w>>(i-1) )&1) 。

例如,对W=10000101010,i=5,j=3 (111),k=1来说 f[5][111]代表的容积是11101010,f[5][111-001]=f[5][110]代表的容积是11000000,剩下的为101010,就应该是f[4][010],这个010就是k向左移一位,加上一下3(i-1)位是不是为1 )

然后注意j是倒序枚举到0,因为表示 j*2^i+(w&((1<< i)-1)),所以j=0是有值的。

这道题提供的二进制分层DP的方式很妙,状态的定义也是可以借鉴的。

(死磕了两天。so sad.)

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int N=1005;
const int M=35;
int n,m,c[N];//n*2^m
LL f[M][N];
int main()
{
    scanf("%d%d",&n,&m);
    while(1)
    {
        if(n==-1&&m==-1) break;
        LL ans=0; 
        memset(f,0,sizeof(f));
        for(int i=1;i<=n;i++)
        {
            int w,val;
            scanf("%d%d",&w,&val);
            int cnt=0;
            while(w%2==0) {w=w/2; cnt++;}
            for(int j=1000;j>=w;j--)
            f[cnt][j]=max(f[cnt][j-w]+val,f[cnt][j]);
            c[cnt]+=w;

        }
        int top=0;
        for(int i=m;i;i>>=1) top++; top--;
        for(int i=1;i<=top;i++) 
        {
            for(int j=1000;j>=0;j--)
            for(int k=0;k<=j;k++)
            f[i][j]=max(f[i][j],f[i][j-k]+f[i-1][min(1000,(k*2)|(m>>(i-1))&1)]);
        }


        printf("%lld\n",f[top][1]);
        scanf("%d%d",&n,&m);        
    }


    return 0;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值