1. 题目来源
2. 题目说明
3. 题目解析
方法一:博弈dp+记忆化+巧妙解法
依稀记得在专业课《运筹学》上学习过 博弈论 相关知识。
由于 Alice
是先取的,且都以 最优策略 取石子,那么就会产生三种情况,即:Alice
第一次 取 1 堆、取 2 堆、取 3 堆分别对应三个结果,那么分别对比这三个情况的结果即可。代码已经过详细注释,提供非注释版。
虽然大佬讲解的很细致,但是 dp
、dfs
基础还是很薄弱,导致听得有点迷糊,索性拿着代码进行单步调试就理解了。主要是一个 深搜+记忆化 的过程,将所有状态全部计算出来。用到深搜肯定离不开回溯,一开始没明白为啥 dp
数组存放的是 Alice - Bob
的得分,却还要将 Alice
的 cur
加上 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";
}
};