问题描述
小Q和小X是很好的朋友,她们正在玩一个游戏。她们拿到了一个数组,游戏开始时小Q随机选择一个元素作为起点。接着,两人轮流行动,小Q先行动。
每次行动时,当前玩家需要选择当前元素左边比它更小的元素,然后移动到该元素,接下来换另一方从这个元素继续移动。如果某一方无法进行合法的移动,则该方输掉游戏。
小Q想知道,在双方都采取最优策略的情况下,她最终获胜的概率是多少?请输出分数的最简形式,即分子和分母互素。如果小Q必胜,则输出 1/1。如果小Q必败,则输出 0/1。
测试样例
样例1:
输入:
n = 5,a = [3, 1, 5, 4, 3]
输出:'3/5'
样例2:
输入:
n = 6,a = [6, 2, 9, 7, 4, 3]
输出:'2/3'
样例3:
输入:
n = 4,a = [8, 5, 6, 3]
输出:'1/4'
先贴结题代码:
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
string solution(int n, vector<int> a) {
vector<int> dp(n, 0);
for (int i = n - 1; i >= 0; --i) {
for (int j = i - 1; j >= 0; --j) {
if (a[j] < a[i] && dp[j] == 0) {
dp[i] = 1;
break;
}
}
}
int win_count = 0;
for (int i = 0; i < n; ++i) {
if (dp[i] == 1) {
win_count++;
}
}
int g = gcd(win_count, n);
return to_string(win_count / g) + "/" + to_string(n / g);
}
问题背景是小Q和小X玩一个数组游戏,小Q和小X轮流行动,小Q首先行动,每次行动时,当前玩家必须选择当前元素左边比它更小的元素,然后跳到那个位置,如果某一方没办法再跳了,也就是说左边没有比当前元素更小的元素了,这个时候就输掉了,要求是在双方都采用最优策略的情况下,小Q获胜的概率。 这道题的核心就是转化为博弈问题,然后通过动态规划求解。
1:博弈的动态规划解法:假设dp[i]表示当前玩家在位置i处时是否能获胜,如果dp[i]=1,说明当前玩家从位置i开始必定能够获胜;如果dp[i]=0,说明当前玩家家从位置 i 开始无法获胜。
2:状态转移:对于每个位置 i,我们要检查它是否有一个左侧的元素 a[j](j<i)满足 a[j] < a[i],且从位置 j 开始是必败的(即 dp[j] == 0)。如果存在这样的元素,那么当前玩家可以跳到位置 j,并且保证对方无法获胜,这样 dp[i] = 1。 3:初始状态:我们从后往前遍历数组来确定每个位置的状态。初始化时,假设所有位置的 dp[i] = 0,表示默认无法获胜。 4:最终计算:小Q的获胜概率是所有 dp[i] = 1 的位置数与总位置数 n 之比。为了输出最简分数,需要计算这两个数的最大公约数并简化分数。
对于题解代码,gcd函数用来计算两个数的最大公约数,用于简化输出的分数。在博弈中,获胜的次数和总次数可能有公约数,通过计算最大公约数可以简化输出结果。dp[i] 记录从位置 i 开始,当前玩家是否必胜。初始时,dp[i] = 0,表示无法获胜。然后从右往左遍历数组,对于每个位置 i,检查它左侧的所有元素是否满足条件(即 a[j] < a[i]),且 dp[j] == 0,如果找到了这样的 j,则更新 dp[i] = 1。遍历 dp 数组,统计 dp[i] = 1 的位置数,得到小Q获胜的次数。然后计算最简分数,输出结果。
类似这道题的博弈问题可以通过动态规划解题,我们需要关注每个位置是否能让当前玩家在最佳策略下获胜,通常在博弈问题中,需要从后往前计算每个状态的输赢情况,这是因为当前状态的输赢往往都依赖于后续的状态,因此从前往后推导有助于确保每个状态都被正确的计算。在一些复杂的博弈问题中,状态转移的复杂度可能会很高。这里通过检查每个元素左边比它小的元素来计算每个位置的胜负情况。对于每个位置,最坏情况下需要遍历所有左边的元素,因此时间复杂度为O(n^2)。