BZOJ1190: [HNOI2007]梦幻岛宝珠

这题是看的题解= =

就是写一下做法吧

发现 “保证每颗宝石的重量符合a*2^b” 这个有些奇怪
考虑从这个条件入手,用跟二进制位有关的东西 dp

仿照 01 背包的做法,dp 的状态用容量来表示

设 f[i][j] 表示已选容量为 j * 2^i ,
并且已选容量的 2^i 之前到 2^0 的二进制位上的每一位都不超过重量上限时
所能获得的最大价值

考虑转移,选一个重量为 ai * 2^bi 的物品的转移方程是

  f[bi][now] = max(f[bi][now], f[bi][now - ai] + vi)

就是跑个 01 背包

按照上面的状态定义,显然要从低位到高位 dp
也就是第一维从小到大做

上边那是同层之间的转移,根据状态定义,低位是要对高位有贡献的
考虑做完一位如何往接下来一位更新

显然二进制位之间是可以互相表示的

下面这步不太自然...

仿照 01 背包的转移,有下面伪代码

 从当前层选体积为[ j ]个 2^i = 从当前层选体积为[ j - k ]个 2^i +

        从下一层选[ k * 2 + 最大容量当前二进制位有1 ]个 2^(i - 1)

这道题这么写就可以过了


 代码:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cstdio>
#include <vector>
#include <locale>
using namespace std;
 
typedef long long ll;
const int MAXN = 105;
 
int n, m;
ll ans;
int w[MAXN], v[MAXN];
ll f[32][1005];
vector<int> bel[32];
 
inline int rd() {
    register int x = 0, c = getchar();
    register bool f = false;
    while (!isdigit(c)) {
        f = (c == '-');
        c = getchar();
    }
    while (isdigit(c)) {
        x = x * 10 + (c ^ 48);
        c = getchar();
    }
    return f ? -x : x;
}
inline void getbel(int No) {
    register int b = 0, num = (w[No] & -w[No]);
    while (!(num & 1) && num) {
        num >>= 1;
        ++b;
    }
    bel[b].push_back(No);
}
inline void clearall() {
    memset(f, 0, sizeof(f));
    for (int i = 0 ;i <= 30; ++i) bel[i].clear();
}
 
int main() {
    while (~(n = rd()) && ~(m = rd())) {
        clearall();
        for (int i = 1; i <= n; ++i) {
            w[i] = rd(); v[i] = rd();
            getbel(i);
        }
        for (int i = 0; i <= 31; ++i) {
            for (int j = 0; j < (int) bel[i].size(); ++j) {
                register int tmp = (w[bel[i][j]] >> i);
                for (int k = 1000; k >= tmp; --k) {
                    f[i][k] = max(f[i][k], f[i][k - tmp] + v[bel[i][j]]);
                }
            }
        }
        for (int i = 1; i <= 31; ++i) {
            for (int j = min(1000, (m >> 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(1000, k * 2 + ((m >> (i - 1)) & 1))]);
                }
            }
        }
        printf("%lld\n", f[31][0]);
    }
    return 0;
}
posted @ 2018-10-26 21:13  EvalonXing  阅读(129)  评论(0编辑  收藏  举报