狀態壓縮DP一般適合的題型的特徵為:一個矩陣,行數較大,列數較小,每個點的狀態只有兩種。正好用一個整型數int來表示每行的一種狀態(其實是其二進位形式,每bit的0和1表示每點的狀態)。不同的是各題的狀態dp的定義,狀態間的限制,狀態的轉換方程。
第一道(Poj 3254):
/**題意:在一片M行N列的草地上(用0和1矩陣表示),1表示能放牛,0表示不能放。 在草地上放牛並且牛不能相鄰,問有多少种放法(一頭牛都不放也算一種)。題解:對於每一行來說,放牛的可能數有2^N種,但是根據以下限制條件就能排除很多: 1.每行中的牛不能相鄰,經計算當N=12時,滿足條件的狀態只有377個 2.每行中放牛要滿足草地的硬體條件,只有1處可放,排除一些 3.上一行中與本行對應的位置處不能放牛,排除一些 由於N值最大為12,所以可以用一個位元來表示一行的狀態,這就是“狀態壓縮”了。 定義dp[i][j]:第i行的狀態為state[j]時,前i行能放牛方法的總數.*/#include <cstdio>#include <queue>#include <algorithm>#include <iostream>#include <cstring>using namespace std;#define MOD 100000000int n, m, row[12];int nState, state[1000];int dp[14][1000];void init(){ int k = 1 << n; nState = 0; for (int i = 0; i < k; i++) if ( (i & (i<<1)) == 0 ) state[nState++] = i;}int main(){ int k; scanf("%d %d", &m, &n); init(); for (int i = 0; i < m; i++) { row[i] = 0; for (int j = n-1; j >= 0; j--) { scanf("%d", &k); row[i] += k << j; } } // 求dp[0],若state[j]滿足第0行草地的硬體條件,則dp[0][j]=1 for (int j = 0; j < nState; j++) { dp[0][j] = ( (row[0] & state[j]) == state[j] ) ? 1 : 0; } // 求dp[i],如果state[j]和第i-1行的狀態state[k]不衝突,則dp[i][j]=Σ(dp[i-1][k]) for (int i = 1; i < m; i++) { for (int j = 0; j < nState; j++) { if ( (row[i] & state[j]) != state[j] ) //判斷是否滿足草地硬體條件 continue; for (int k = 0; k < nState; k++) { if ( dp[i-1][k] && (state[k]&state[j]) == 0 ) dp[i][j] = (dp[i][j] + dp[i-1][k]) % MOD; } } } int res = 0; for (int j = 0; j < nState; j++) { if ( dp[m-1][j] ) res = (res + dp[m-1][j]) % MOD; } printf("%d\n", res); return 0;}
第二道(Poj 1185):
/**題意:在一個n行m列的矩陣中,字元'P'處能放炮兵,字元'H'處不能放炮兵。 並且如果兩個炮兵在一條(水平或垂直)直線上時,它們的距離不能小於2,問最多放多少個兵。由於在求第i行時,它的狀態要收到第i-1行和i-2行的影響,所以定義一個三維dp:dp[i][j][k]表示第i行的狀態為state[j],第i-1行的狀態為state[k]時,前i行能放炮兵的最大數量。for ( ... i < n ...){ for ( ... j < nState ... ) { 如果狀態j和第i行的地形不衝突,那麼: for ( ... k < nState ... ) { 如果第i行狀態j和第i-1行狀態k不衝突,那麼: for ( ... h < nState ... ) { 如果第i行狀態j和第i-2行狀態h不衝突,那麼: 在dp[i-1][k][h]中找最大的一個賦值給dp[i][j][k],再加上state[j]中1的個數 } } }}*/#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;int n, m, dp[105][80][80]; //dp[i][j][k]表示第i行的狀態為j,第i-1行的狀態為k時能放炮兵的最大數量int nState, state[80], num[80]; //10位二進位位中各個1之間的距離不小於2,這樣的數只有60個。 //依次存放在state[]中,num[i]表示state[i]中1的個數//在小於2^m的數中找1之間的距離不小於2的數,儲存在state[]中void init(){ int k = 1 << m; nState = 0; for (int i = 0; i < k; i++) if ( (i&(i<<1)) == 0 && (i&(i<<2)) == 0 ) { state[nState] = i; num[nState] = 0; int j = i; while (j) { num[nState] += j % 2; j /= 2; } nState++; }}int main(){ int row[105]; char str[15]; while ( cin >> n >> m ) { init(); for (int i = 0; i < n; i++) { row[i] = 0; scanf("%s", str); for (int j = 0; j < m; j++) if (str[j] == 'P') row[i] += 1 << j; } memset(dp, 0, sizeof(dp)); // 計算dp[0] for (int j = 0; j < nState; j++) { if ( (state[j] & row[0]) != state[j] ) continue; for (int k = 0; k < nState; k++) dp[0][j][k] = num[j]; } // 計算dp[1] if (n > 1) for (int j = 0; j < nState; j++) { if ( (state[j] & row[1]) != state[j] ) continue; for (int k = 0; k < nState; k++) { if ( (state[j] & state[k]) == 0 ) dp[1][j][k] = dp[0][k][0] + num[j]; } } // 計算dp[>1] for (int i = 2; i < n; i++) { for (int j = 0; j < nState; j++) { if ( (state[j] & row[i]) != state[j] ) continue; for (int k = 0; k < nState; k++) { if ( state[j] & state[k] ) continue; for (int h = 0; h < nState; h++) { if ( state[j] & state[h] ) continue; if ( dp[i-1][k][h] > dp[i][j][k] ) dp[i][j][k] = dp[i-1][k][h]; } dp[i][j][k] += num[j]; } } } // 在dp[n-1]中找最大值 int max = 0; for (int j = 0; j < nState; j++) { for (int k = 0; k < nState; k++) if (max < dp[n-1][j][k]) max = dp[n-1][j][k]; } printf("%d\n", max); }}
---------------------- 更多“狀態壓縮DP”題目... ------------------------