P1879 [USACO06NOV] Corn Fields G
題目描述
農場主John新買了一塊長方形的新牧場,這塊牧場被劃分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一塊正方形的土地。John打算在牧場上的某幾格裡種上美味的草,供他的奶牛們享用。
遺憾的是,有些土地相當貧瘠,不能用來種草。並且,奶牛們喜歡獨佔一塊草地的感覺,於是John不會選擇兩塊相鄰的土地,也就是說,沒有哪兩塊草地有公共邊。
John想知道,如果不考慮草地的總塊數,那麼,一共有多少種種植方案可供他選擇?(當然,把新牧場完全荒廢也是一種方案)
輸入格式
第一行:兩個整數M和N,用空格隔開。
第2到第M+1行:每行包含N個用空格隔開的整數,描述了每塊土地的狀態。第i+1行描述了第i行的土地,所有整數均為0或1,是1的話,表示這塊土地足夠肥沃,0則表示這塊土地不適合種草。
輸出格式
一個整數,即牧場分配總方案數除以100,000,000的餘數。
輸入輸出樣例
輸入 #1
2 3
1 1 1
0 1 0
輸出 #1
9
一道入門的狀壓dp題,但對於我這種萌新還是太難了,所以寫篇部落格紀念一下。。。
看到m的範圍很小,我們就可以考慮對m進行狀態壓縮
我們設 \(f[i][j]\) 表示第\(i\)行放置情況為\(j\)的方案數.
我們就可以列舉所有的狀態來進行轉移
也就是 \(f[i][j] += f[i-1][k]\)
那怎麼判斷一個狀態是否合法呢?
首先,這一行不能選相鄰的兩列也就是 j & (j<<1) == 0, j & (j>>1) == 0
並且還要與上一行的所選的不能有相鄰的即 j & k == 0
那麼怎麼判斷這個狀態選的土地是否都合法呢?
我們可以預先處理每一行的初始狀態,即對土地能不能選進行狀態壓縮。
當 s[i] & j == 0 就代表j這個狀態可能選了不肥沃的土地。(可以畫個圖瞭解一下)
最終答案就是 \(\sum_{i=0}^{i< (1<<m)} f[n][i]\)
程式碼
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int p = 1e8; int n,m,ans,x,base[20],f[15][35000],zhuangtai[20],map[20][20]; inline int read() { int s = 0, w = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();} return s * w; } bool judge(int x)//判斷x這個狀態是否合法 { if(x & (x<<1)) return 0; if(x & (x>>1)) return 0; return 1; } int main() { n = read(); m = read(); for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { map[i][j] = read(); } } for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { zhuangtai[i] = (zhuangtai[i]<<1) + map[i][j];//把一開始土地能不能選的情況進行狀態壓縮 } } base[0] = 1; for(int i = 1; i <= m; i++) base[i] = base[i-1] * 2; f[0][0] = 1; for(int i = 1; i <= n; i++) { for(int j = 0; j < base[m]; j++) { if(judge(j) && ((zhuangtai[i] & j) == j))//j這個狀態不能選不肥沃的土地 { for(int k = 0; k < base[m]; k++) { if(((j&k) == 0) && judge(k)) { f[i][j] += f[i-1][k];//轉移 f[i][j] %= p; } } } } } for(int i = 0; i < base[m]; i++) ans = (ans + f[n][i]) % p; printf("%d\n",ans); return 0; }