P3188 [HNOI2007] 梦幻岛宝珠-题解

20230918

P3188 [HNOI2007] 梦幻岛宝珠

Statement

01背包, \(n \le 100\),但是容量 \(m \le 2^{30}\)

物体的体积可以写成 \(a \times 2^b(a \le 10,b \le 30)\)

Solution

发现 \(W\) 太大了,而 \(a,b\) 又很小,

那我们考虑对背包进行分组。

\(f_{i,j}\) 维护第 \(i\) 组容量为 \(j \times 2^i\) 的答案。

每一组就直接用普通背包做就可以了。

现在考虑如何合并?

\(2^{i-1}\) 转移到 \(2^i\) 时,我们同样用 \(f_{i,j}\) 表示 \(2^{0 \sim i}\) 位, \(j \times 2^i\) 时的答案。

我们考虑可以从 \(2^{i-1}\) 那里拿一些上来,

假设拿上来 \(k \times 2^i\) ,于是:

\[f_{i,j}=\max_{k=0}^{j}\{ f_{i-1,k \times 2+[W\&(1<<(i-1))]}+f_{i,j-k}\} \]

其中 \([W\&(1<<(i-1))]\) 是如果 \(W\) 里面本来可以有 \(2^{i-1}\)

我就可以多拿上来一个。

于是这道题就做完了。

时间复杂度 \(O(T \times len \times 1000 \times 1000)\)

/*https://www.cnblogs.com/hwy0622/p/17713057.html*/
#include <bits/stdc++.h>
using namespace std;
#define int long long

int n,W,len=0,w;
struct node{
  int a,b,val;
  bool operator <(const node &rhs) const{
    return b<rhs.b;
  }
}a[2005];
int f[50][1005];

signed main(){
  /*2023.9.18 H_W_Y P3188 [HNOI2007] 梦幻岛宝珠 分组背包*/ 
  while(scanf("%lld%lld",&n,&W)){
  	if(n==-1||W==-1) break;
  	for(int i=1;i<=n;i++){
  	  scanf("%lld%lld",&w,&a[i].val);
	  a[i].a=w/(w&(-w));  
	  a[i].b=0;
	  for(w=(w&(-w))>>1;w;w>>=1) a[i].b++;
	}
  	memset(f,0,sizeof(f));
	for(int j=0;j<=1000;j++) f[0][j]=0;
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)//直接枚举 
	  for(int j=1000;j>=a[i].a;j--)
	    f[a[i].b][j]=max(f[a[i].b][j],f[a[i].b][j-a[i].a]+1ll*a[i].val);
	len=0;
	for(int s=W>>1;s;s>>=1) len++;
	for(int i=1;i<=len;i++){
	  for(int j=1000;j>=0;j--)
	    for(int k=0;k<=j;k++)
	      f[i][j]=max(f[i][j],f[i-1][min(1000ll,(k<<1)|((W&(1<<(i-1)))!=0))]+f[i][j-k]);	//必须要加上!=0	
	}
	printf("%lld\n",f[len][1]);
  }
  return 0;
}

Conclusion

这道题有特殊性质,我们可以考虑把它分组。

在合并的时候注意上一个带上来的贡献……

思路还是挺妙的~

posted @ 2023-09-18 21:00  H_W_Y  阅读(48)  评论(0编辑  收藏  举报