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;
}