BZOJ 1190 梦幻岛宝珠(分层背包)

86 篇文章 0 订阅
15 篇文章 0 订阅

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

题解:
这个题。
其实所有的背包都可以把每个物品的重量变成 a ∗ 2 b a*2^b a2b,如果有什么只基于二进制的高明解法那么就可以去拿图灵奖了。
但是这个题的特殊点就是 a < = 10 a<=10 a<=10
但是还是很难。
如果 a = 0   o r   1 a=0\ or \ 1 a=0 or 1
那么这就是一个数位DP(还是 O ( n ) O(n) O(n)的)。
a < = 10 a<=10 a<=10时仍然考虑从高位到低位的DP。
f i , j f_{i,j} fi,j表示剩余的容量在 j 2 i j2^i j2i ( j + 1 ) 2 i − 1 (j+1)2^i-1 (j+1)2i1之间的最大价值和。
为什么是之间,因为之下的价值还没有定,也不用定,因为这个和当前层的任何物品能否放进去都没有关系,也就是说之下的价值因为没有存的价值所以我们就不存。
然后在一层层往低位转移的时候按总容量在这一位是1还是0来确定(清晰)容量的这一位。
这就是分层DP。
在信息对于每一层的需求量是逐层递增的时候,对于信息选择性忽视。
但是这并没有什么用,因为背包问题是NPC问题,复杂度指数增长的本性并没有更改。
这时我们回想 a < = 10 a<=10 a<=10有什么用。
所有 a a a的和最大为 10 n = 1000 10n = 1000 10n=1000

我们DP时 j 2 i j2^i j2i会转移到 ( j − a ) 2 i (j-a)2^i (ja)2i
那么当 j > = 1000 j>=1000 j>=1000时,我们可以知道它永远不会转移到负数,也就是背包永不会被填满。
那么 j > = 1000 j>=1000 j>=1000时就按照 j = 1000 j = 1000 j=1000来计算也可以得到正确的答案,反正都填不满。
那么复杂度就是 O ( 1000 ∗ 30 n ) O(1000 * 30n) O(100030n)

普通的背包问题也是有了一个比较小的上界才可以有高效算法的。
不冤。

A C   C o d e AC\ Code AC Code

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
 
int n,L,f[32][1005];
 
void chk(int &a,int b){
    a = max(a , b);
}
 
int main(){
    while(~scanf("%d%d",&n,&L) && n!=-1){
        vector<int>v[31],w[31];
        for(int i=1,a,b;i<=n;i++){
            scanf("%d%d",&a,&b);
            int t = 0;
            for(;a%2==0;a>>=1) t++;
            v[t].pb(b) , w[t].pb(a);
        }
        int T=0;
        memset(f,-0x3f,sizeof f);
        f[31][0] = 0;
        for(int i=30;i>=0;i--){
            for(int j=0;j<=1000;j++)
                if(f[i+1][j] >= 0)
                    chk(f[i][min(j<<1|(L>>i&1),1000)] , f[i+1][j]);
            for(int j=0;j<v[i].size();j++){
                for(int k=0;k<=1000;k++)
                    chk(f[i][k],f[i][min(k+w[i][j],1000)] + v[i][j]);
            }
        }
        printf("%d\n",*max_element(f[0],f[0]+1001));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值