*题解:P3188 [HNOI2007] 梦幻岛宝珠

题目链接

解析

0/1 背包但是重量很大但是都能表示为 \(a \times 2 ^ b\) 的形式且 \(a \le 10,b \le 30\)

可以发现,如果所有 \(b\) 都相同那么还是普通的 0/1 背包。现在有不同的 \(b\),于是可以按 \(b\) 分组,分别看作不同的 0/1 背包。状态是设 \(f_{i,j}\) 表示重量不超过 \(2^i \times j\) 的最大价值。

考虑合并,设 \(g_{i,j}\) 表示从 \(0\) 考虑到 \(i\),重量不超过 \(2^i \times j\) 的最大价值。于是有:

\[g_{i,j} = \max_{k=0}^{j} f_{i,j - k} + g_{i - 1,2k} \]

但是这样做显然有非常大的局限性,无法精确刻画上界,这样的话最后就不好求重量为 \(W\) 时的价值了。怎么办呢?利用拆位的思想,若 \(W\) 的第 \(i\) 位为 \(1\),则在转移时重量额外加上 \(2^i\),否则不变。具体地:

\[g_{i,j} = \max_{k=0}^{j} f_{i,j - k} + g_{i - 1,2k +[W \operatorname{and} 2^{i - 1}=2^{i - 1}]} \]

时间复杂度 \(O(Tn^2a^2b)\)\(T\) 为数据组数。

实现时所有操作在 \(f\) 上进行也是可行的,但要注意转移顺序。

代码

/*
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 1000 + 5,M = 4000005,P = 2000000,mod = 998244353;
int a[N],f[N][N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,W;
	while(cin>>n>>W){
		if(n == -1){
			return 0;
		}
		memset(f,0,sizeof(f));
		for(int i=1;i<=n;i++){
			int w,v;
			cin>>w>>v;
			for(int j=0;j<=30;j++){
				if(w % (1 << j) == 0){
					int x = w / (1 << j);
					if(x > 10) continue;
					for(int k=N - 1;k>=x;k--){		
						f[j][k] = max(f[j][k],f[j][k - x] + v);
					} 
					break;
				}
			}
		}
		for(int i=1;i<=30;i++){
			for(int j=N - 1;j>=0;j--){
				for(int k=0;k * 2 + bool(W & (1 << (i - 1)))<N && k <= j;k++){
					f[i][j] = max(f[i][j],f[i][j - k] + f[i - 1][k * 2 + bool(W & (1 << (i - 1)))]);
				}
			}
		}
		for(int i=30;i>=0;i--){
			if(W & (1 << i)){
				cout<<f[i][1]<<'\n';
				break;
			}
		}
	}
	return 0;
}
posted @ 2026-05-30 12:43  yutar  阅读(10)  评论(0)    收藏  举报