洛谷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;
}
原文地址:https://www.cnblogs.com/tuchen/p/10415034.html