• 博弈问题dp模版


    博弈问题动态规划通用思路

    转载:来自大佬https://leetcode-cn.com/problems/stone-game/solution/jie-jue-bo-yi-wen-ti-de-dong-tai-gui-hua-tong-yong/

    • 该问题主要是如何设计dp数组,其次是动态转移方程

    1.dp数组设计

    • 状态有三种:开始堆的索引i,结束堆的索引j,以及当前轮到的人

    dp[i][j].fir 表示,对于 piles[i...j] 这部分石头堆,先手能获得的最高分数。
    dp[i][j].sec 表示,对于 piles[i...j] 这部分石头堆,后手能获得的最高分数。
    
    举例理解一下,假设 piles = [3, 9, 1, 2],索引从 0 开始
    dp[0][1].fir = 9 意味着:面对石头堆 [3, 9],先手最终能够获得 9 分。
    dp[1][3].sec = 2 意味着:面对石头堆 [9, 1, 2],后手最终能够获得 2 分。

    2.动态转移方程

    • 状态不断转换:A选手从[i,...,j]堆中先手取完之后,B选手则从[i+1,...,j]或[i,....,j-1]堆中先手取,这样交替进行。

    • A选手:

      • A选手从[i,...,j]堆中先手取完之后,A选手的状态将变为从[i+1,...,j]或[i,....,j-1]堆中后手取

      • 如果A选手想要先手,则可得的最高分数 dp[i][j].fir = max(piles[i] + dp[i+1][j].sec,piles[j] + dp[i][j-1].sec)

    • B选手:

      • B选手则从[i+1,...,j]或[i,....,j-1]堆中先手取的状态等价于B选手从[i,...,j]堆中后手取。

      • 如果剩余[i+1,...,j],则dp[i][j].sec = dp[i+1][j].fir

      • 如果剩余[i,...,j-1],则dp[i][j].sec = dp[i][j-1].fir

    dp[i][j].fir = max(piles[i] + dp[i+1][j].sec, piles[j] + dp[i][j-1].sec)
    dp[i][j].fir = max(    选择最左边的石头堆     ,     选择最右边的石头堆     )
    # 解释:我作为先手,面对 piles[i...j] 时,有两种选择:
    # 要么我选择最左边的那一堆石头,然后面对 piles[i+1...j]
    # 但是此时轮到对方,相当于我变成了后手;
    # 要么我选择最右边的那一堆石头,然后面对 piles[i...j-1]
    # 但是此时轮到对方,相当于我变成了后手。
    
    if 先手选择左边:
        dp[i][j].sec = dp[i+1][j].fir
    if 先手选择右边:
        dp[i][j].sec = dp[i][j-1].fir
    # 解释:我作为后手,要等先手先选择,有两种情况:
    # 如果先手选择了最左边那堆,给我剩下了 piles[i+1...j]
    # 此时轮到我,我变成了先手;
    # 如果先手选择了最右边那堆,给我剩下了 piles[i...j-1]
    # 此时轮到我,我变成了先手。

    3.初始条件

    • 由动态转移方程可知,dp[i][j]由左侧dp[i][j-1]和下侧dp[i+1][j]决定,则要先初始化斜对角线元素,最终求得dp[0][n-1],比较dp[0][n-1].fir与dp[0][n-1].sec哪个大,哪个大就是获胜方

     

     4.代码

    • n*n的矩阵(可有为1维,但是n维操作更加清晰)

    class Pair {
        int fir, sec;
    
        Pair(int fir, int sec) {
            this.fir = fir;
            this.sec = sec;
        }
    }
    
    class Solution {
        /* 返回游戏最后先手和后手的得分之差 */
        public boolean stoneGame(int[] piles) {
            int n = piles.length;
            // 初始化 dp 数组
            Pair[][] dp = new Pair[n][n];
            for (int i = 0; i < n; i++)
                for (int j = i; j < n; j++)
                    dp[i][j] = new Pair(0, 0);
            // 填入基本数据(斜对角线数据)
            for (int i = 0; i < n; i++) {
                dp[i][i].fir = piles[i];
                dp[i][i].sec = 0;
            }
            // 斜着遍历数组
            for (int l = 2; l <= n; l++) {          //斜对角线元素个数从n-1开始递减
                for (int i = 0; i <= n - l; i++) {  //行从[0,...,n-2]开始缩小
                    int j = l + i - 1;
                    // 先手选择最左边或最右边的分数
                    int left = piles[i] + dp[i + 1][j].sec;
                    int right = piles[j] + dp[i][j - 1].sec;
                    // 套用状态转移方程
                    if (left > right) {
                        dp[i][j].fir = left;
                        dp[i][j].sec = dp[i + 1][j].fir;
                    } else {
                        dp[i][j].fir = right;
                        dp[i][j].sec = dp[i][j - 1].fir;
                    }
                }
            }
            Pair res = dp[0][n - 1];
            return res.fir > res.sec;
        }
    
    }
    • 1*n矩阵

    class Pair {
        int fir, sec;
    
        Pair(int fir, int sec) {
            this.fir = fir;
            this.sec = sec;
        }
    }
    
    class Solution {
        /* 返回游戏最后先手和后手的得分之差 */
        public boolean stoneGame(int[] piles) {
    
            int n = piles.length;
            // 初始化 dp 数组
            Pair[] dp = new Pair[n];
            for (int i = 0; i < n; i++) {
                dp[i] = new Pair(0, 0);
            }
    
            // 遍历数组
            for (int i = n - 1; i >= 0; i--) {
                for (int j = i; j < n; j++) {
                    if(i == j){
                        // 填入基本数据(斜对角线数据)
                        dp[j].fir = piles[i];
                        dp[j].sec = 0;
                    }else {
                        int d1 = piles[i] + dp[j].sec;
                        int d2 = piles[j] + dp[j - 1].sec;
                        if (d1 > d2) dp[j].sec = dp[j].fir;  //这里要用到dp[j].fir的旧值
                        else dp[j].sec = dp[j - 1].fir;
                        dp[j].fir = Math.max(d1, d2); //注意这行,必须要在dp[j].sec赋值完才能执行,因为上面可能会用到它的旧值
                    }
                }
            }
    
            Pair res = dp[n - 1];
            return res.fir > res.sec;
        }
    
    }
  • 相关阅读:
    朱晔和你聊Spring系列S1E10:强大且复杂的Spring Security(含OAuth2三角色+三模式完整例子)
    朱晔和你聊Spring系列S1E9:聊聊Spring的那些注解
    朱晔和你聊Spring系列S1E8:凑活着用的Spring Cloud(含一个实际业务贯穿所有组件的完整例子)
    朱晔和你聊Spring系列S1E7:简单好用的Spring Boot Actuator
    朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP
    朱晔和你聊Spring系列S1E5:Spring WebFlux小探
    朱晔和你聊Spring系列S1E4:灵活但不算好用的Spring MVC
    朱晔和你聊Spring系列S1E3:Spring咖啡罐里的豆子
    朱晔和你聊Spring系列S1E2:SpringBoot并不神秘
    朱晔的互联网架构实践心得S1E10:数据的权衡和折腾【系列完】
  • 原文地址:https://www.cnblogs.com/zhihaospace/p/12811716.html
Copyright © 2020-2023  润新知