[LeetCode 周赛183] 4. 石子游戏 III(博弈dp、记忆化、巧妙解法)

文章目录1. 题目来源2. 题目说明3. 题目解析方法一:博弈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 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& stoneValue) {
int n = stoneValue.size();

// 初始化
// vis表示该变量是否已经被记忆化
// dp表示该变量的值
for (int i = 0; i <= n; ++i)
for (int k = 0; k 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 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& stoneValue) {
int n = stoneValue.size();

for (int i = 0; i <= n; i++)
for (int k = 0; k 0) return "Alice";
if (ans < 0) return "Bob";
return "Tie";
}
};


作者:Y_puyu

相关推荐

使用VSCode开发和调试.NET Core程序的方法

使用VSCode开发和调试.NET Core程序的方法

Vue项目vscode 安装eslint插件的方法(代码自动修复)

Vue项目vscode 安装eslint插件的方法(代码自动修复)

Android Studio 恢复小窗口停靠模式(Docked Mode)

vscode写python时的代码错误提醒和自动格式化的方法