算法:State Compression

上週參加了學校的acm比賽,只做出兩道水題,看了答案發現其中有兩道題用到了狀態壓縮,於是就去學習了一波。

狀態壓縮

狀態壓縮就是將題目的模型抽象成狀態的集合,如果每個對象只有兩個狀態且對象數量較少,可以用0和1分別代表這兩種狀態,用一個二進制數可以表示所有對象的狀態,其中位數等於對象的個數。狀態壓縮往往與動態規劃聯繫起來,下面以LeetCode的一道題作爲例子。

Can I Win

問題

你和小夥伴正在玩一個遊戲,首先有1至n共n個數,你們輪流取一個數並對所有已經取出的數求和,如果大於m,那麼最後取數的人獲勝,取出的數不能重複。給出n和m,如果你先取數,並且雙方均採取最佳策略,判斷你最終能否獲勝。

思路

首先這是一個雙方博弈的過程,由於雙方都採取最佳策略,那麼只有在無論對方如何操作你都能獲勝的情況下才算必勝。然後因爲取出的數不能重複,所以需要存儲這n個數的使用情況。如果用0表示未取出,1表示已取出,那麼就可以用一個二進制數來表示取數狀態,即狀態壓縮。最後用動態規劃和深度優先搜索來求解。需要注意兩種特殊情況:如果m小於或等於1,那麼你必勝;如果m大於所有數的和,那麼雙方必敗。

代碼

// 判斷n的第i位是否爲1
bool is1(int n, int i) {
	return (n & 1 << i) != 0;
}

// 把n的第i位設置爲1
int set1(int n, int i) {
    return n | (1 << i);
}

// 表示每種狀態的勝負情況,0表示未確定,1表示必勝,-1表示必敗
int dp[1 << 20] = {};

// m爲最大數,t爲求和目標,k爲當前狀態
bool dfs(int m, int t, int k) {
    if (t <= 0) return false;  // 若求和目標小於或等於0,說明前一個回合對方已經獲勝,因此己方必敗
    if (dp[k] != 0) return dp[k] > 0;  // 己方的勝負情況已確定

    for (int i = 0; i < m; ++i) {
	    // 若狀態中某一位不爲1,說明對應的數未取出
	    // 取出一個未取出的數,求和目標減去這個數,然後判斷對方的勝負情況,如果對方必敗,那麼己方必勝
        if (!is1(k, i) && !dfs(m, t-i-1, set1(k, i))) {
            dp[k] = 1;
            return true;
        }
    }
    
    // 如果無論如何對方必勝,那麼己方必敗
    dp[k] = -1;
    return false;
}

bool canIWin(int maxChoosableInteger, int desiredTotal) {
    if (desiredTotal <= 1) return true;  // 如果求和目標小於或等於1,你必勝
    int sum = maxChoosableInteger * (maxChoosableInteger+1) / 2;
    if (sum < desiredTotal) return false;  // 如果求和目標大於所有數的和,雙方必敗
    return dfs(maxChoosableInteger, desiredTotal, 0);
}

總結

狀態壓縮用二進制數表示狀態,然後與動態規劃結合求解,使複雜的問題簡單化。雖然思路比較複雜,但非常有用,我還需要多做練習。

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