1. 程式人生 > >【BZOJ1190】夢幻島寶珠(HNOI2007)-揹包DP+思維

【BZOJ1190】夢幻島寶珠(HNOI2007)-揹包DP+思維

測試地址:夢幻島寶珠
做法: 本題需要用到揹包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;
}