Sharing Chocolate UVALive - 4794 分享巧克力 狀壓dp

 題目鏈接

給出一塊長爲x, 寬爲y的矩形巧克力, 每次操作可以沿一條直線把一塊巧克力切割成兩塊長寬均爲整數的巧克力(一次不能同時切割多塊巧克力) 。問: 是否可以經過若干次操作得到n塊面積分別爲a1,a2,…, an的巧克力。 如圖1-44所示, 可以把3×4的巧克力切成面積分別爲6,3, 2,1的4塊。
圖 1-44
【輸入格式】

輸入包含若干組數據。 每組數據的第一行爲一個整數n(1≤n≤15) ;第二行爲兩個整數x和y(1≤x,y≤100) ; 第三行爲n個整數a1,a2,…,an。 輸入結束標誌爲n=0。
【輸出格式】
對於每組數據, 如果可以切割成功, 輸出“Yes”, 否則輸出“No”。
【分析】
注意到n的規模很小, 可以把與n有關的子集作爲動態規劃狀態的一部分。 設f(r,c, S) 表示r行c列的巧克力是否可以切割成面積集合S。 樣例1的答案爲Yes, 即f(3,4, {6,3, 2,1} ) =1。 第一刀把巧克力切成了3×3和3×1兩塊, 即f(3,3, {6,2, 1} ) =f(3,1, {3} ) =1。
     不難得到下面的狀態轉移規則: f(r,c, S) =1當且僅當

❑ 存在1≤r0<r和S的子集S0, 使得f(r0,c, S0) =f(r-r0,c, S-S0) =1, 或者

❑ 存在1≤c0<c和S的子集S0, 使得f(r,c0,S0) =f(r,c-c0,S-S0) =1。

     前者對應橫着切, 後者對應豎着切。 狀態有O(xy2n) 個, 每個狀態轉移到O(x+y) 個狀態, 總時間複雜度爲O((x+y)xy2n) , 有些偏大。
     其實, 上述狀態有些浪費。 如果r×c不等於S中所有元素之和(記爲sum(S) ) , 顯然f(r,c, S) =0。 換句話說, 我們可以只計算r*c=sum(S) 的狀態f(r,c, S) 。 另外,f(r,c, S) =f(c,r, S) , 所以不妨設r≤c, 然後用g(r,S) 代替f(r,c, S) 。 這樣, 狀態降爲了O(x2n) 個。 在枚舉決策時, 一旦確定了S0, 實際上可以計算出r0或者c0(或者發現不存在這樣的r0或者c0) , 因此總決策數爲O(x3n) , 這也是本算法的時間複雜度。 由於很多狀態達不到, 推薦用記憶化搜索實現,實際運算量往往遠小於O(x3n) 。
    最後有一點需要注意, 輸入之後需要比較所有ai之和是否爲x×y(想一想, 爲什麼) 。

#include <cstdio>
#include <cstring>
#include <algorithm> 
using namespace std;
const int N = 16, M = 100+5;
int a[N], sum[1<<N], d[1<<N][M], vis[1<<N][M];

int bitCount(int x){
	return x == 0 ? 0 : bitCount(x>>1) + (x&1);
}

int dp(int s,int x){
	if(vis[s][x]) return d[s][x];
	vis[s][x] = 1;
	int& ans = d[s][x];	
	if(bitCount(s) == 1) return ans = 1; 
	int y = sum[s] / x;
	for(int s0 = (s-1)&s; s0; s0 = (s0-1)&s){//枚舉s的子集 
		int s1 = s - s0; 
		if(sum[s0]%x == 0 && dp(s0,min(x,sum[s0]/x)) && dp(s1, min(x,sum[s1]/x))) return ans = 1;
		if(sum[s0]%y == 0 && dp(s0,min(y,sum[s0]/y)) && dp(s1, min(y,sum[s1]/y))) return ans = 1;	
	}
	return ans = 0;
}

int main(int argc, char** argv) {
	int kase = 0, n;
	while( ~scanf("%d",&n) && n){
		int x, y;
		scanf("%d%d",&x,&y);
		for(int i = 0; i < n; i++)
			scanf("%d",&a[i]);
		memset(sum,0,sizeof(sum)); // 每個子集中的元素之和
		for(int i = 0, len = 1 << n; i < len; i++)
			for(int j = 0; j < n; j++)
				if((1<<j) & i)  sum[i] += a[j];
		int all = (1<<n) - 1;
		memset(vis,0,sizeof(vis));
		int ans;
		if(sum[all] != x*y || sum[all]%x != 0) ans = 0;
		else ans = dp(all, min(x,y));
		printf("Case %d: %s\n", ++kase, ans ? "Yes" : "No");
	}
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章