在任意一名玩家进行操作之后,剩余的所有石子的价值总和不会发生变化。对于每一步操作,玩家可以把「最左侧」的 x 枚石子进行「价值合并」,并获得与「价值」等价的分数。因此,最左侧的石子的价值一定是 stones 的某一个前缀和,即玩家在每一轮获得的分数一定是初始数组 stones 的某一个前缀和。
可以将题目中的游戏转化成如下等价的形式:
求出 stones 的前缀和数组 pre,其中:
Alice 和 Bob 依次在数组 pre 上进行操作,并且 Alice 先手。在一次操作中,当前玩家可以选择一个下标 u,获得 pre[u] 的分数:
如果当前玩家是 Alice 并且是首次操作,那么 u 不能为 0。对应到题目中的游戏规则,即为「选择的石子数量 x 必须大于 1」;
对于其余的情况,如果对手的上一次操作选择的下标是 v,那么必须有 u>v。对应到题目中的游戏规则,即为「对手上一次操作合并了若干枚石子,使得最左侧的石子的价值为 pre[v]」,同时「当前玩家合并了从最左侧的石子开始,到原本在数组 stones 中下标为 u 的石子为止的所有石子,使得最左侧的石子的价值为 pre[u]」。
设数组 stones 和 pre 的长度为 n,如果当前玩家选择了下标 n−1,那么它就会合并剩余的所有石子,游戏结束。
可以使用基于博弈思想的动态规划解决上述的游戏,设 f(i) 表示当 Alice 可以选择的下标 u 在 [i,n) 范围内时,Alice 与 Bob 分数的最大差值,在进行状态转移时,可以考虑 Alice 是否选择了 i 作为下标 u;
如果 Alice 没有选择 i 作为下标 u,那么她需要在 [i+1,n) 的范围内进行选择,因此有状态转移方程:
如果 Alice 选择了 i 作为下标 u,那么她获得了 pre[i] 的分数,并且轮到 Bob 在剩余的范围 [i+1,n) 中进行选择。由于 Bob 会采用最优策略,因此在 [i+1,n) 的范围内,Bob 与 Alice 分数的最大差值就为 f[i+1],因此有状态转移方程:
由于 Alice 会采用最优策略,因此状态转移选择二者中的较大值:
从 i=n−1 开始倒序地计算所有的状态,最终的答案即为 f[1]。
C++ 示例:
class Solution {
public:intstoneGameVIII(vector<int>& stones){
int n = stones.size();
vector<int> pre;partial_sum(stones.begin(), stones.end(),back_inserter(pre));
vector<int>f(n);
f[n -1]= pre[n -1];for(int i = n -2; i >=1;--i){
f[i]=max(f[i +1], pre[i]- f[i +1]);}return f[1];}};
int[] sum =newint[n +1];for(int i =0; i < n; i++){
sum[i +1]= sum[i]+ stones[i];}
游戏过程不妨先不考虑时间的要求,直接通过暴力模拟来解决。
暴力法直接模拟游戏过程,需要注意每一轮得到的结果都是这一轮的玩家期望得分差值的最大值。如果当前已经取到第 i (1 <= i <= n) 块石子,那么这一轮可以取到的结果 solve(i) 就是从 i 到 n 中选择一个位置 j,使得 sum[j] - (下一轮对手的得分)最大,这里的 sum[j] 就是这一轮的得分,由于要保证双方均采用最优策略,下一轮对手也会选择最大的得分差值,所以相当于求解 sum[j] - solve(j + 1) 的最大值。
Java 示例:
classSolution{
int n;int[] stones;int[] sum;publicintstoneGameVIII(int[] stones){
n = stones.length;this.stones = stones;
sum =newint[n +1];for(int i =0; i < n; i++) sum[i +1]= sum[i]+ stones[i];returnsolve(2);}publicintsolve(int idx){
if(idx == n)return sum[idx];int res = sum[n];for(int i = idx; i < n; i++){
res =Math.max(res, sum[i]-solve(i +1));}return res;}}