蒙德里安的夢想【狀態壓縮DP】

題目鏈接:291. 蒙德里安的夢想

求把NM的棋盤分割成若干個12的的長方形,有多少種方案。

例如當N=2,M=4時,共有5種方案。當N=2,M=3時,共有3種方案。

如下圖所示:(圖在鏈接裏.)

輸入格式
輸入包含多組測試用例。

每組測試用例佔一行,包含兩個整數N和M。

當輸入用例N=0,M=0時,表示輸入終止,且該用例無需處理。

輸出格式

每個測試用例輸出一個結果,每個結果佔一行。

數據範圍

1≤N,M≤11

輸入樣例:

1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0

輸出樣例:

1
0
1
2
3
5
144
51205

程序說明:

核心思想是先放橫着的長方形,放好以後再插入豎着的長方形。那麼總方案數就等於只放橫着的小方塊的合法方案數。

f[i][j]表示已經將 i - 1 列擺好,且從第 i - 1 列伸出到第 i 列的所有方案的狀態是 j 。其中 j 是二進制數,j 的每一位對應網格中每一列,如果前一列伸出到第 i 列,則 j 爲1,反之爲0。

狀態的轉移需要看前一列的情況,即f[i - 1][k],其中k表示第 i - 2列伸出到第 i - 1 列的情況。只有當 j 和 k 不同時爲1,即放橫着的長方形不衝突時才能進行下去。

判斷當前方案是否合法需要看每一列的空白部分是否爲偶數,如果是偶數的話,才能放入豎着的長方形。

放完m - 1列就已經全部放完了,最後的答案是f[m][0](即前m - 1列已經放完,且沒有長方形伸出到外面去)。

代碼如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 12, M = 1 << N;
long long f[N][M];
int n, m, st[M]; //st[]儲存是否能放入豎着的長方形

int main() {
	while(cin>>n>>m && n || m) {
		//預處理出每種情況下是否能放入豎着的長方形
		for(int i = 0; i < 1 << n; i++) {
			st[i] = 1;
			int cnt = 0;
			for(int j = 0; j < n; j++) {
				if(i >> j & 1) { //如果這一位是1,即已經被佔了
					if(cnt & 1) { //空白部分爲奇數
						st[i] = 0;
						cnt = 0;
					}
				}
				else 
					cnt++;
			}
			if(cnt & 1)
				st[i] = 0;
		}
		memset(f, 0, sizeof f);
		f[0][0] = 1; //空白的方案數是1
		for(int i = 1; i <= m; i++)	
			for(int j = 0; j < 1 << n; j++)
				for(int k = 0; k < 1 << n; k++) //k枚舉前一列的情況
					if((j & k) == 0 && st[j | k])
						f[i][j] += f[i - 1][k];
						
		cout<<f[m][0]<<endl;
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章