洛谷P3188 梦幻岛宝珠
这道题真的难写啊,细节超级多
一看就不能直接背包
考虑到所有的重量都是a*2^b的形式
所以可以按照数位进行分层dp
设f[i][j]表示容量j*2^i时最大的价值(此时的状态)
在每层dp内,只转移b为i的物品
f[i][j]=max(f[i][j],f[i][j-a]+val)
每层处理完之后,考虑层间转移
转移之前,第i层代表的容量为j*2^i,转移之后还应该加上总容量W的前i-1层容量,变为j*2^i+w&((1<<i)-1)
考虑第i层有(j-k)*2^i的容量装载第i层的物品,那还剩下k*2^i的容量去装载前i-1层的物品
在第i-1层表示剩余容量k*2^i,需要转化一下:
当前第i层用了(j-k)*2^i的容量,转化后应该用了j*2^i+w&((1<<i)-1)的容量,减一下,就是前i-1层应该剩下的容量,设为p
又因为第i-1层的容量表示为x*2^(i-1),所以解一下x*2^(i-1)=p就行了->x=2*k+((w>>(i-1))&1)
f[i][j]=max(f[i][j],f[i-1][2*k+((w>>(i-1))&1)])
答案就是最终的f[up][1]了,此时的j=1代表了总容量W
细节问题:
在枚举的时候,容量j应该倒序枚举,保证当前只有第i层的物品(因为要枚举当前层物品用了j*2^i的容量)
呼~
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<queue> #include<stack> #include<cmath> using namespace std; typedef long long ll; const int maxn = 1010; ll n,W,up,ans; ll f[110][1100]; // 第 i 位(2^i) , 背包中容量 j*2^i 的最大价值 ll w[maxn],val[maxn]; vector<int> b[maxn]; ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f;} int main(){ while(1){ n=read(),W=read(); if(n==-1&&W==-1) break; memset(f,0,sizeof(f)); for(int i=1;i<=n;i++){ w[i]=read(),val[i]=read(); int sum=0; while(w[i]%2==0) w[i]/=2,sum++; for(int j=1000;j>=w[i];j--){ f[sum][j]=max(f[sum][j],f[sum][j-w[i]]+val[i]); } } up=0; for(int i=W;i;i>>=1) up++; up--; for(int i=1;i<=up;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(1000ll,k*2+((W>>(i-1))&1))]); } } } printf("%lld\n",f[up][1]); } return 0; }