1. 程式人生 > >POJ Corn Fields 狀態壓縮DP基礎題

POJ Corn Fields 狀態壓縮DP基礎題

分別是 進制 得到 代碼 ble end true names ostream

題目鏈接:http://poj.org/problem?id=3254
題目大意(名稱什麽的可能不一樣,不過表達的意思還是一樣的):
種玉米
王小二從小學一年級到現在每次考試都是班級倒數第一名,他的爸爸王大強覺得讀書對於王小二來說應該是沒有出路了,於是決定讓王小二繼承自己的衣缽,從事一份非常有前途的工作——種玉米。王大強是一位富有的農場主,他擁有一塊 M*N 平方米的矩形田地專門用來種玉米(1<=M,N<=12)。這個玉米地被分成了 M*N 個,每個格子是一個大小為1平方米的格子。這塊玉米地中有一些格子因為還沒有開墾,所以上面長滿了巖石,所以這些格子是不適合種玉米的。相鄰的格子也是不能同時種玉米的,因為如果這麽做了的話,兩個格子區域的玉米因為相互搶奪土壤裏面的資源結果會都長不好。

雖然王小二的成績不好,但是他是一個喜歡思考的人,他想要知道一共有多少種可行的方式來種玉米。

輸入

第一行包括兩個正數M和N。
接下來M行每行包含N個整數,數字1表示這個格子是適合種玉米的,數字0表示這個格子不適合種玉米。

輸出

輸出一個整數,表示種玉米的方案數。(除 100,000,000 取模)

樣例輸入

2 3
1 1 1
0 1 0

樣例輸出

9

題目分析:這道題目可以用狀態壓縮DP來處理。
dp[i][j] 表示第i行的狀態取為j的時候的方案總數,那麽這個j的狀態是什麽意思呢?
j雖然是一個整數,但是它其實表示的是一行的所有二進制狀態。
比如,如果我們某一行的三個格子分別是:種、不種、種,我們用 1 表示“種”,用 0

表示“不種”, 那麽這一行其實可以表示成 1,0,1 對應的二進制數就是 101 ,這個數對應的十進制的數就是 1*4+0*2+1*1 = 5
我們還要過濾一下每一行的狀態,第i行的狀態j成立的條件是:

  • j對應的狀態上沒有在有巖石的格子上種玉米;
  • j對應的狀態不會有連續的兩列上種玉米。

這兩點我在代碼上面有比較明確的體現。
然後我們就可以得到狀態轉移方程:

dp[i][j] = 1, 其中i==0(第0行),j是第i行的合理狀態(滿足上面提到的兩個條件)
dp[i][j] = sum(dp[i-1][k]),其中i>0,j是第i行的合理狀態,k是第i-1行的所有合理狀態的集合,並且j和k狀態滿足關系式:j&k==0,對應的字面意思就是不存在連續的兩行同一列都種著玉米

最後的結果就是 sum(dp[M-1][j]),其中j對應第M-1的所有合理狀態。

代碼:

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int maxn = 13;
const int MOD = 100000000;

int M, N, dp[maxn][1<<maxn], g[maxn][maxn];
vector<int> states[maxn];

void test() {
    for (int i = 0; i < M; i ++) {
        cout << (i+1) << "(" << states[i].size() << "): ";
        int sz = states[i].size();
        for (int j = 0; j < sz; j ++) {
            if (j) cout << ",";
            cout << states[i][j];
        }
        cout << endl;
    }
    cout << "check:" << endl;
    for (int i = 0; i < states[M-1].size(); i ++) {
        if (dp[M-1][i]) {
            cout << "\t" << i << " : " << dp[M-1][i] << endl;
        }
    }
}

int main() {
    while (cin >> M >> N) {
        memset(dp, 0, sizeof(dp));
        for (int i = 0; i < M; i ++) {
            for (int j = 0; j < N; j ++) {
                cin >> g[i][j];
            }
        }
        for (int i = 0; i < M; i ++) {
            states[i].clear();
            for (int j = 0; j < (1<<N); j ++) {
                bool flag = true;
                // 判斷是否在巖石上種了玉米
                for (int k = 0; k < N; k ++) {
                    if (g[i][k] == 0 && j&(1<<k)) {
                        flag = false;
                        break;
                    }
                }
                // 判斷是否相鄰的格子都種了玉米
                if (flag) {
                    for (int k = 0; k < N-1; k ++) {
                        if (j&(1<<k) && j&(1<<(k+1))) {
                            flag = false;
                            break;
                        }
                    }
                }
                if (flag) {
                    states[i].push_back(j);
                }
            }
        }
        int sum = 0;
        for (int i = 0; i < M; i ++) {
            int sz = states[i].size();
            for (int j = 0; j < sz; j ++) {
                int p = states[i][j];
                if (i == 0) dp[i][p] = 1;
                else {
                    int sz2 = states[i-1].size();
                    for (int k = 0; k < sz2; k ++) {
                        int q = states[i-1][k];
                        if (!(p&q)) {
                            dp[i][p] += dp[i-1][q];
                            dp[i][p] %= MOD;
                        }
                    }
                }
                if (i == M-1) {
                    sum += dp[i][p];
                    sum %= MOD;
                }
            }
        }
        cout << sum << endl;
        // test();
    }
    return 0;
}

POJ Corn Fields 狀態壓縮DP基礎題