#背包,位运算#洛谷 3188 [HNOI2007]梦幻岛宝珠

题目


分析

既然对于每个\(w_i\)都能被分解为\(a*2^b\)
那么考虑维护关于\(b\)的背包,再将关于\(b\)的背包统计为关于\(b+1\)的背包


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
int n,W,dp[31][1011],sw[101],w[101],c[101],ls[32],nxt[101];
inline signed iut(){
    rr int ans=0,f=1; rr char c=getchar();
    while (!isdigit(c)) f=(c=='-')?-f:f,c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans*f;
}
inline signed min(int a,int b){return a<b?a:b;}
inline signed max(int a,int b){return a>b?a:b;}
signed main(){
    while (1){
        memset(dp,0,sizeof(dp));
        memset(sw,0,sizeof(sw));
        memset(ls,0,sizeof(ls));
        n=iut(); W=iut(); rr int len;
        if (n==-1&&W==-1) return 0;
        for (rr int i=1,two;i<=n;++i){
            w[i]=iut(),c[i]=iut();
            for (two=0;!(w[i]&1);++two) w[i]>>=1;
            nxt[i]=ls[two],ls[two]=i,sw[two]+=w[i];
        }
        for (rr int i=0;i<=30;++i)
        for (rr int j=ls[i];j;j=nxt[j])
        for (rr int k=sw[i];k>=w[j];--k)
            dp[i][k]=max(dp[i][k],dp[i][k-w[j]]+c[j]);
        for (rr int i=1,t;i<=30;++i){
            sw[i]+=(sw[i-1]+1)>>1,t=(W>>(i-1))&1;
            for (rr int j=sw[i];j>=0;--j)
            for (rr int k=0;k<=j;++k)
                dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[i-1][min(sw[i-1],k<<1|t)]);//如果第$i-1$位为1还能够补1
        }
        for (len=0;W>>len;++len); --len;
        printf("%d\n",dp[len][1]);
    }
}
posted @ 2020-11-03 19:46  lemondinosaur  阅读(80)  评论(0编辑  收藏  举报