【BZOJ1190】梦幻岛宝珠(HNOI2007)-背包DP+思维

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Maxwei_wzj/article/details/83274676

测试地址:梦幻岛宝珠
做法: 本题需要用到背包DP+思维。
这道题看上去是一个裸01背包,然而容量特别大,因此我们只能从其中唯一一个特殊条件入手: a 2 b a\cdot 2^b 形式的重量。
我们考虑把这些物品分阶段来进行决策。我们首先对每个 b b ,求出重量表示为 a 2 b a\cdot 2^b 的那些物品中,取重量为 k 2 b k\cdot 2^b 的物品能得到的最大总价值 t o t v a l ( b , k ) totval(b,k) ,这就是一个一般的01背包了,因为 a 10 , n 100 a\le 10,n\le 100 ,所以时间复杂度最多是 1 0 6 10^6 的级别。接下来,我们从低位向高位DP,令 f ( i , j ) f(i,j) 为取用 b i b\le i 的物品,容量为 j 2 i j\cdot 2^i 加上 W W 在第 i i 位以下的所有容量时所能得到的最大总价值。上面 W W 在第 i i 位以下的容量,就指的是它在第 i i 位以下的上界(用位运算解释就是 W & ( ( 1 < < i ) 1 ) W\&((1<<i)-1) )。在转移的时候,我们首先枚举当前的 j j ,然后看前一位有哪些可以转移到当前状态的状态。显然,由于要满足第 i i 位之前是上界的条件,第 i 1 i-1 位应该和 W W 的第 i 1 i-1 位相同,这才能转移。于是我们就写出来了一个状态转移的式子,就解决了这道题,时间复杂度虽然看上去有点大,但均摊下来还是跑得很快的。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,W,totw[35],f[35][2100],len;
vector<int> w[35],val[35];

int main()
{
	while(scanf("%d%d",&n,&W)&&n>=0&&W>=0)
	{
		len=0;
		for(int i=0;i<35;i++)
			w[i].clear(),val[i].clear();
		memset(totw,0,sizeof(totw));
		memset(f,0,sizeof(f));
		
		for(int i=1;i<=n;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			int j=0;
			while(!(x&1)) {j++;x>>=1;}
			w[j].push_back(x);
			totw[j]+=x;
			val[j].push_back(y);
			len=max(len,j);
		}
		while(W>>len) len++;
		len--;
		
		for(int i=0;i<=len;i++)
			for(int j=0;j<(int)w[i].size();j++)
				for(int k=totw[i];k>=w[i][j];k--)
					f[i][k]=max(f[i][k],f[i][k-w[i][j]]+val[i][j]);
		
		for(int i=1;i<=len;i++)
		{
			totw[i]+=((totw[i-1]+1)>>1);
			for(int j=totw[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(totw[i-1],(k<<1)|((W>>(i-1))&1))]);
		}
		printf("%d\n",f[len][1]);
	}
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Maxwei_wzj/article/details/83274676