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

题目很明显是一个背包问题,但是其背包容量很大,是 1 0 10 10^{10} 1010 级别的。
这样明显不是普通背包算法可以应付得来的,但我们又不能不用背包来解决。
一定有什么地方可以优化。

我们观察题目给的条件,发现题目保证 ∑ n ≤ 2000 \sum n \leq 2000 n2000,且每一个物品的重量都可以拆分为 a × 2 b a \times 2^b a×2b 的形式,其中 a ≤ 10 , b ≤ 30 a \leq 10,b \leq 30 a10,b30

这样我们就可以将每一个物品按照 b b b 来分组,对于每一个组内我们跑一个背包,然后再把这些组再跑一个背包就可以了。

对于每一组内跑背包的时候,我们背包的容量就是当前这一组内物品个数的十倍,因为所有的 a a a 都不超过10,所以 ∑ a \sum a a 肯定也是小于当前组内物品个数10倍的。

做组间背包的时候,我们从低位到高位做。
我们想,每一次我们都从当前位的容量内拿出来 k k k 去给下面的部分做背包。对于剩余的容量做完背包之后,我们就将下面对容量为 k k k 的背包做出来的结果直接加上来即可。

具体实现的时候,我们定义两个二维数组 f f f g g g

f [ i ] [ k ] f[i][k] f[i][k] 代表的是对从低到高第 i i i 位(或者说 b = i b=i b=i)的物品进行容量为 k k k 的背包得出的结果。

f f f 数组的时候我们就直接按照正常背包做就可以了。

g [ i ] [ j ] g[i][j] g[i][j] 代表的就是当使用 b b b i i i 的物品进行容量为 j j j 的背包时, b b b 小于 i i i 的所有物品占满了 W W W 的后 i i i 位所代表的容量的值。

举个例子,我们最后要输出的结果就是 g [ ⌊ log ⁡ 2 W ⌋ ] [ 1 ] g[\lfloor \log_2{W} \rfloor][1] g[⌊log2W⌋][1] 的值。而这个代表我们对最高位上做背包时,背包容量为1(也只能是1),同时除了最高位以外的所有位代表的容量都被占满了的时候的最大值。

而在更新 g g g 数组的时候,我们遵循下面的转移方程:

g [ i ] [ j ] = max ⁡ ( g [ i ] [ j ] , f [ i ] [ j − k ] + g [ i − 1 ] [ min ⁡ ( 10 n , 2 k + ( W 2 i − 1 & 1 ) ) ] ) g[i][j] = \max(g[i][j],f[i][j-k]+g[i-1][\min(10n,2k+(\frac{W}{2^{i-1}} \& 1))]) g[i][j]=max(g[i][j],f[i][jk]+g[i1][min(10n,2k+(2i1W&1))])

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N = 110, M = 40;
int n, c;
int w[N], v[N];
vector<pair<int, int> >h[M];
int f[M][N * M];
int g[M][N * M];
int lowbit(int x)
{
	return x & -x;
}
void init()
{
	memset(w, 0, sizeof(w));
	memset(v, 0, sizeof(v));
	memset(f, 0, sizeof(f));
	memset(g, 0, sizeof(g));
	for(int i = 0; i < M; i++)h[i].clear();
}
int main()
{
	scanf("%d%d", &n, &c);
	while(n != -1 && c != -1)
	{
		init();
		for(int i = 1; i <= n; i++)
		{
			scanf("%d%d", &w[i], &v[i]);
			int b = log2(lowbit(w[i]));
			h[b].push_back(make_pair(w[i] >> b, v[i]));
		}
		int cnt = log2(c);

		for(int t = 0; t <= cnt; t++)
			for(int i = 0; i < h[t].size(); i++)
				for(int j = 10 * h[t].size(); j >= h[t][i].first; j--)
					f[t][j] = max(f[t][j], f[t][j - h[t][i].first] + h[t][i].second);
		//给每一组内跑背包
		for(int i = 0; i <= 10 * h[0].size(); i++)
			g[0][i] = f[0][i];
		for(int t = 1; t <= cnt; t++)
			for(int j = 0; j <= 10 * n; j++)
				for(int i = 0; i <= j; i++)
					g[t][j] = max(g[t][j], f[t][j - i] + g[t - 1][min(10 * n, i * 2 + ((c >> (t - 1)) & 1))]);
		//跑组与组之间的背包
		printf("%d\n", g[cnt][1]);
		scanf("%d%d", &n, &c);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值