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
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值