嘘~ 正在从服务器偷取页面 . . .

洛谷P3188 [HNOI2007]梦幻岛宝珠 题解


洛谷P3188 [HNOI2007]梦幻岛宝珠 题解

题目链接:P3188 [HNOI2007]梦幻岛宝珠

题意

给你 $n$ 颗宝石,每颗宝石都有重量和价值。要你从这些宝石中选取一些宝石,保证总重量不超过 $W$,且总价值最大,并输出最大的总价值。

【数据范围】
对于 $100\%$ 的数据,$1\le n \le 100$,$1\le W,w_i,v_i \le 2^{30}$。
保证每个 $w_i$ 能写成 $a \times 2^b\space (a,b \in \mathbb N)$ 的形式,$a \leq 10$ , $b \leq 30$,且答案不超过 $2^{30}$。

注:下文中将使用 $m$ 代替 $W$

这么大的背包容量,肯定不是朴素的背包问题

可以发现数据范围里强调了 “保证每个 $w_i$ 能写成 $a \times 2^b\space (a,b \in \mathbb N)$ 的形式”

尽管有

但是这里的 $a\le 10$ 却十分特殊,这意味着我们可以从这个角度思考解法

考虑利用泛化物品的思想

首先将每件物品按 $b$ 进行分组,在每一组内dp

设 $f[i][j]$ 为在所有满足 $b=i$ 的物品中选择若干(不妨设为 $k$ )件物品,且总体积 $j^{\prime}$ 满足

时的最大价值。

然后考虑合并泛化物品,即 $b$ 不同的物品之间的转移

令 $g[i][j]$ 表示仅使用 $b \in [0,i]$ 的物品,且 $b=i$ 的物品所占空间为 $j$ ,$b\in [0,i-1]$ 的物品所占空间为 $m\&(2^i-1)$ 时的最大价值

这里的 $\&$ 表示二进制按位与,不难发现 $m\&(2^i-1)$ 就是 $m$ 的第 $1$ 到 $i-1$ 位

转移方程如下

注:这里的第 $i$ 位是从第 $1$ 位开始数的

写成代码就是

g[i][j]=max(g[i][j],f[i][j-k]+g[i-1][min(10*n,k*2+((m>>(i-1))&1))]);

即从 $j\times 2^i$ 中抽出来 $k\times 2^i$ 的体积给 $b \in[0,i-1]$ 的物品

这里加上 $\min(10n,\dots)$ 的原因是 $(\dots)$ 处可能超出了 $\sum\limits_{0\le k \le i-1} j_k$ 的一个较宽松的上限 $10n$

但是这个上界并不容易达到,因此为了避免讨论,使用 $\min$ 即可

因此答案为 $g[m\text{ 的位数}][1]$

时间复杂度 $O(Qn^2\log m)$ ,但是常数巨大无比(别急,后面还有…

代码1:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
namespace FastIO
{
    #define gc() readchar()
    #define pc(a) putchar(a)
    #define SIZ (int)(1e6+15)
    char buf1[SIZ],*p1,*p2;
    char readchar()
    {
        if(p1==p2)p1=buf1,p2=buf1+fread(buf1,1,SIZ,stdin);
        return p1==p2?EOF:*p1++;
    }
    template<typename T>void read(T &k)
    {
        char ch=gc();T x=0,f=1;
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
        while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
        k=x*f;
    }
    template<typename T>void write(T k)
    {
        if(k<0){k=-k;pc('-');}
        static T stk[66];T top=0;
        do{stk[top++]=k%10,k/=10;}while(k);
        while(top){pc(stk[--top]+'0');}
    }
}using namespace FastIO;
#define N (int)(115)
int n,m,num[32];
int a[32][N],v[32][N],f[32][N*10],g[32][N*10];
#define mem(x) memset(x,0,sizeof(x));
void clear(){mem(a);mem(num);mem(v);mem(f);mem(g);}
signed main()
{
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    while(read(n),read(m),n+m!=-2)
    {
        clear();
        for(int i=1,x,y; i<=n; i++)
        {
            read(x);read(y);
            int cnt=0;
            while(!(x&1)){x>>=1,++cnt;}
            ++num[cnt];
            a[cnt][num[cnt]]=x;
            v[cnt][num[cnt]]=y;
        }
        int cnt=32;
        while(m<=(1ll<<(--cnt)));
        for(int k=0; k<=cnt; k++)
            for(int i=1; i<=num[k]; i++)
                for(int j=10*num[k]; j>=a[k][i]; j--)
                    f[k][j]=max(f[k][j],f[k][j-a[k][i]]+v[k][i]);
        for(int i=0; i<=10*num[0]; i++)
            g[0][i]=f[0][i];
        for(int i=1; i<=cnt; i++)
            for(int j=0; j<=10*n; j++)
                for(int k=0; k<=j; k++)
                    g[i][j]=max(g[i][j],f[i][j-k]+g[i-1][min(10*n,k*2+((m>>(i-1))&1))]);           
        write(g[cnt][1]);pc('\n');
    }
    return 0;
}

注意到上面的代码,虽然可以过

但是一共跑了1.20s这是不可接受的(逃

考虑优化一下

注意到这个上界 $10n$ 一般不容易达到(前面提到过

因此我们会做很多无效枚举

可以开一个数组 $s[i]$ 表示所有 $b=i$ 的物品的 $a$ 之和

同时,注意到 $f$ 其实可以重复利用,考虑改变一下枚举顺序

这样就可以在 39ms 左右跑出来啦!

代码2:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
namespace FastIO
{
    #define gc() readchar()
    #define pc(a) putchar(a)
    #define SIZ (int)(1e6+15)
    char buf1[SIZ],*p1,*p2;
    char readchar()
    {
        if(p1==p2)p1=buf1,p2=buf1+fread(buf1,1,SIZ,stdin);
        return p1==p2?EOF:*p1++;
    }
    template<typename T>void read(T &k)
    {
        char ch=gc();T x=0,f=1;
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
        while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
        k=x*f;
    }
    template<typename T>void write(T k)
    {
        if(k<0){k=-k;pc('-');}
        static T stk[66];T top=0;
        do{stk[top++]=k%10,k/=10;}while(k);
        while(top){pc(stk[--top]+'0');}
    }
}using namespace FastIO;
#define N (int)(115)
int n,m,num[32],s[32];
int a[32][N],v[32][N],f[32][N*10];
#define mem(x) memset(x,0,sizeof(x));
void clear(){mem(a);mem(num);mem(v);mem(f);mem(s);}
signed main()
{
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    while(read(n),read(m),n+m!=-2)
    {
        clear();
        for(int i=1,x,y; i<=n; i++)
        {
            read(x);read(y);
            int cnt=0;
            while(!(x&1)){x>>=1,++cnt;}
            ++num[cnt];s[cnt]+=x;
            a[cnt][num[cnt]]=x;
            v[cnt][num[cnt]]=y;
        }
        int cnt=32;
        while(m<=(1ll<<(--cnt)));
        for(int k=0; k<=cnt; k++)
            for(int i=1; i<=num[k]; i++)
                for(int j=s[k]; j>=a[k][i]; j--)
                    f[k][j]=max(f[k][j],f[k][j-a[k][i]]+v[k][i]);
        for(int i=1; i<=cnt; i++)
        {
            s[i]+=(s[i-1]+1)>>1; // 向上取整
            for(int j=s[i]; 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(s[i-1],(k<<1)|((m>>(i-1))&1))]);
        }
        write(f[cnt][1]);pc('\n');
    }
    return 0;
}

这道题我花了几乎一整天研究(菜死了

网上好多的题解都有错

希望这篇可以帮助到你们

如果有错欢迎指出!

参考文献

[1] https://www.luogu.com.cn/blog/youare251-1/solution-p3188

[2] https://www.luogu.com.cn/user/50047


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录