[LeetCode 周賽183] 4. 石子游戲 III(博弈dp、記憶化、巧妙解法)

1. 題目來源

鏈接:5379. 石子游戲 III

2. 題目說明

在這裏插入圖片描述
在這裏插入圖片描述

3. 題目解析

方法一:博弈dp+記憶化+巧妙解法

依稀記得在專業課《運籌學》上學習過 博弈論 相關知識。

由於 Alice 是先取的,且都以 最優策略 取石子,那麼就會產生三種情況,即:Alice 第一次 取 1 堆、取 2 堆、取 3 堆分別對應三個結果,那麼分別對比這三個情況的結果即可。代碼已經過詳細註釋,提供非註釋版。

雖然大佬講解的很細緻,但是 dpdfs 基礎還是很薄弱,導致聽得有點迷糊,索性拿着代碼進行單步調試就理解了。主要是一個 深搜+記憶化 的過程,將所有狀態全部計算出來。用到深搜肯定離不開回溯,一開始沒明白爲啥 dp 數組存放的是 Alice - Bob 的得分,卻還要將 Alicecur 加上 dfs 的下一個值。其實這就是 dfs 的特性了,當 dfs 越界後返回 0,那麼 Alice 從後向前回溯就是最後一堆石子的得分,此時 Bob 沒選擇,所以 Alice 得正分,同理 Bob 得負分。

開始回溯時,即前半段都是兩者取 1 堆的情況,後面就是取 2 堆、取 3 堆進行一個最大值、最小值的比較。確實很抽象。

真的是理解不了的,進行一個簡單的單步調試,中間輸出 num 一些中間參數,就能很容易理解這個思想了。

其實理解了這個博弈思想,是個很簡單的題目,可惜…建議把石子合併前幾道系列題做了,理解下簡單的動規、博弈思想會好很多。大佬們都評價這道題難度只能算個 Medium

確實我的理解也不到位,也有大佬很簡潔的一個 dp 方程就搞定了,我還是先把前導題啃了再理解理解吧。希望我上面的個人理解對讀者有所幫助…

參見代碼如下:

// 執行用時 :1196 ms, 在所有 C++ 提交中擊敗了100.00%的用戶
// 內存消耗 :143.2 MB, 在所有 C++ 提交中擊敗了100.00%的用戶

const int MAXN = 50000 + 50;

bool vis[MAXN][2]; 
// dp數組,dp[i][k]表示當前取到第i堆石子,當前爲第k個人取,k=0表示Alice,k=1表示Bob
// dp數組存放值爲當前Alice分數減去Bob分數,Alice取石子即最大化dp值,Bob取石子即最下化dp值即可
int dp[MAXN][2]; // Alice - Bob
vector<int> num;

// 深搜
// 當前取到第x堆石子,共有n堆石子,輪到誰取
int dfs(int x, int n, int turn) {
    if (vis[x][turn]) return dp[x][turn];
    if (x >= n) return 0;
    
    vis[x][turn] = true;
    // Alice取
    if (turn == 0) {
        // 讓dp數組先存取一堆的情況,Alice取x堆的石子,即num[x],Bob就需要從x+1堆開始取了
        int cur = num[x];
        // 在此理解cur + dfs(x + 1, n, turn ^ 1);就是深搜,深搜至最後確定最後一個狀態,dfs(x + 1, n, turn ^ 1)越界後爲0
        // 若最後一個爲Alice取,則當前Alice爲正值,否則Bob爲負值
        dp[x][turn] = cur + dfs(x + 1, n, turn ^ 1);
        for (int i = x + 1; i < n && i < x + 3; ++i) {
            cur += num[i];
            // Alice需要最大化dp值,dp值爲Alice減Bob的分數
            dp[x][turn] = max(dp[x][turn], cur + dfs(i + 1, n, turn ^ 1));
        }
    }
    else {
        // Bob也是先取一堆石子
        int cur = num[x];
        // Bob分數爲Alice取下一堆石子的分數減去Bob的當前取的分數,因爲dp數組爲Alice-Bob的分數
        dp[x][turn] = dfs(x + 1, n, turn ^ 1) - cur;
        for (int i = x + 1; i < n && i < x + 3; ++i) {
            cur += num[i];
            dp[x][turn] = min(dp[x][turn], dfs(i + 1, n, turn ^ 1) - cur);
        }
    }
    return dp[x][turn];
}

class Solution {
public:   
    string stoneGameIII(vector<int>& stoneValue) {
        int n = stoneValue.size();
        
        // 初始化
        // vis表示該變量是否已經被記憶化
        // dp表示該變量的值
        for (int i = 0; i <= n; ++i) 
            for (int k = 0; k < 2; k++) 
                dp[i][k] = 0, vis[i][k] = false;

        num = stoneValue;
        
        int ans = dfs(0, n, 0);
        if (ans > 0) return "Alice";
        if (ans < 0) return "Bob";
        return "Tie";
    }
};

參見代碼如下:

const int MAXN = 50000+ 50;

bool vis[MAXN][2]; 
int dp[MAXN][2]; // Alice - Bob
vector<int> num;

int dfs(int x, int n, int turn){
    if (vis[x][turn]) return dp[x][turn];
    if (x >= n) return 0;
    
    vis[x][turn] = true;
    
    if (turn == 0){
        int cur = num[x];
        dp[x][turn] = cur + dfs(x + 1, n, turn ^ 1);
        for (int i = x + 1; i < n && i < x + 3; i++){
            cur += num[i];
            dp[x][turn] = max(dp[x][turn], cur + dfs(i + 1, n, turn ^ 1));
        }
    }else{
        int cur = num[x];
        dp[x][turn] = dfs(x + 1, n, turn ^ 1) - cur;
        for (int i = x + 1; i < n && i < x + 3; i++){
            cur += num[i];
            dp[x][turn] = min(dp[x][turn], dfs(i + 1, n, turn ^ 1) - cur);
        }
    }
    
    return dp[x][turn];
}

class Solution {
public:   
    string stoneGameIII(vector<int>& stoneValue) {
        int n = stoneValue.size();
        
        for (int i = 0; i <= n; i++) 
            for (int k = 0; k < 2; k++) 
                dp[i][k] = 0, vis[i][k] = false;
        num = stoneValue;
        
        int ans = dfs(0, n, 0);
        
        if (ans > 0) return "Alice";
        if (ans < 0) return "Bob";
        return "Tie";
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章