BZOJ2446. 石头剪刀布 数学+概率+分数规划(二分+dp check)
BZOJ2446. 石头剪刀布
题面:
描述:
\(A\)和\(B\)进行石头剪刀布的游戏。
游戏按照每轮进行,每轮进行\(n\)局,如果\(A\)先比\(B\)多获胜\(m_1\)轮,\(A\)赢得游戏;如果\(B\)先比\(A\)多获胜\(m_2\)轮,\(B\)赢得游戏。
对于每轮,进行\(n\)局游戏,已知每局\(B\)出石头、剪刀和布的概率,每局如果\(A\)赢,则\(A\)得一分;如果\(B\)赢,则\(B\)得一分;如果平局,则无人得分。最后得分多的人此轮获胜,得分相同此轮无人获胜。
求\(A\)获胜的最大概率。
输入:
读入包含多组数据。
对于每组数据:
第一行包含\(3\)个数,\(n,m_1,m_2\),如题面描述。
接下来\(n\)行,每行\(3\)个数,分别表示\(B\)每局出石头、布与剪刀的百分率,保证三个数之和为\(100\)。
整个输入以0 0 0
结束
输出:
对于每组数据输出一行,表示获胜的百分率,保留\(3\)位小数。
样例输入:
2 1 1
50 50 0
0 0 100
1 2 1
20 20 60
5 3 1
32 47 21
25 37 38
34 40 26
49 20 31
25 60 15
5 3 1
41 13 46
13 27 60
50 24 26
14 32 54
25 56 19
10 3 1
21 33 46
31 25 44
24 32 44
39 39 22
21 39 40
30 25 45
49 21 30
30 24 46
20 33 47
43 28 29
10 3 1
30 20 50
44 23 33
31 28 41
28 48 24
35 26 39
29 34 37
47 31 22
32 43 25
25 29 46
30 42 28
362 91 3
34 35 31
33 33 34
33 33 34
33 34 33
33 32 35
33 35 32
34 33 33
31 35 34
32 35 33
35 34 31
33 34 33
34 31 35
33 34 33
33 35 32
32 34 34
34 34 32
32 35 33
32 34 34
32 35 33
34 32 34
35 32 33
34 35 31
35 34 31
34 31 35
35 34 31
33 34 33
32 35 33
33 33 34
32 33 35
34 32 34
35 34 31
35 33 32
34 33 33
34 34 32
34 32 34
33 32 35
32 33 35
35 34 31
32 35 33
35 34 31
35 33 32
35 31 34
33 34 33
32 33 35
35 32 33
32 34 34
32 35 33
32 34 34
33 35 32
34 34 32
33 34 33
35 34 31
32 34 34
34 32 34
35 33 32
33 34 33
34 35 31
33 33 34
35 32 33
35 34 31
33 33 34
31 35 34
33 33 34
34 35 31
33 34 33
32 35 33
32 34 34
33 33 34
31 35 34
34 31 35
34 34 32
35 32 33
33 32 35
34 32 34
35 34 31
35 31 34
34 33 33
34 32 34
31 35 34
35 32 33
34 33 33
32 35 33
33 35 32
33 35 32
33 34 33
32 33 35
32 33 35
31 34 35
33 33 34
35 31 34
31 35 34
34 31 35
35 31 34
32 34 34
34 35 31
35 33 32
34 31 35
34 31 35
34 31 35
34 33 33
33 33 34
34 31 35
33 34 33
35 33 32
32 35 33
32 33 35
31 34 35
33 35 32
34 32 34
34 34 32
33 32 35
35 34 31
34 35 31
32 33 35
32 35 33
34 31 35
35 32 33
34 31 35
34 35 31
31 34 35
35 33 32
31 35 34
34 35 31
34 31 35
35 33 32
34 33 33
34 31 35
34 31 35
34 34 32
33 34 33
34 32 34
34 35 31
34 34 32
32 35 33
35 32 33
35 32 33
35 31 34
35 32 33
33 33 34
31 35 34
32 33 35
31 34 35
34 33 33
34 32 34
32 33 35
33 34 33
34 33 33
35 32 33
32 33 35
32 34 34
32 35 33
33 35 32
35 33 32
34 31 35
32 33 35
33 35 32
31 35 34
34 31 35
33 34 33
34 35 31
33 32 35
34 32 34
33 35 32
34 35 31
31 35 34
33 33 34
32 34 34
34 33 33
35 33 32
34 34 32
33 33 34
32 34 34
33 32 35
34 31 35
34 32 34
33 32 35
32 34 34
35 32 33
34 31 35
34 32 34
33 32 35
34 32 34
35 31 34
34 33 33
31 35 34
33 35 32
32 34 34
31 34 35
33 34 33
34 34 32
34 32 34
35 31 34
32 33 35
34 31 35
34 34 32
32 34 34
32 34 34
32 33 35
31 34 35
32 34 34
33 35 32
33 33 34
35 33 32
31 35 34
35 32 33
31 34 35
32 34 34
33 32 35
35 32 33
35 31 34
34 32 34
32 34 34
31 34 35
34 35 31
34 34 32
35 31 34
31 35 34
33 34 33
34 34 32
33 33 34
35 33 32
34 33 33
34 33 33
34 33 33
33 35 32
35 34 31
33 33 34
34 35 31
31 35 34
32 35 33
32 33 35
35 34 31
33 33 34
35 32 33
32 33 35
34 31 35
35 34 31
34 32 34
31 34 35
35 34 31
35 31 34
32 33 35
33 33 34
32 33 35
34 31 35
31 35 34
35 32 33
34 35 31
33 32 35
34 32 34
33 32 35
34 33 33
33 33 34
31 35 34
35 34 31
35 32 33
34 31 35
32 35 33
34 34 32
35 33 32
33 35 32
34 31 35
33 34 33
34 33 33
34 35 31
34 32 34
32 35 33
32 33 35
35 32 33
34 32 34
33 33 34
33 33 34
35 31 34
34 35 31
34 35 31
35 31 34
34 32 34
31 34 35
33 33 34
32 34 34
34 32 34
32 35 33
33 32 35
33 32 35
33 33 34
35 33 32
31 34 35
35 31 34
33 34 33
31 34 35
34 35 31
31 34 35
32 35 33
33 35 32
34 35 31
32 35 33
34 32 34
32 33 35
32 34 34
35 33 32
32 35 33
33 32 35
35 34 31
34 35 31
33 33 34
33 32 35
32 33 35
33 34 33
35 32 33
31 35 34
31 34 35
32 35 33
33 34 33
31 35 34
33 34 33
34 33 33
33 33 34
32 34 34
32 35 33
33 35 32
34 31 35
34 35 31
31 34 35
35 33 32
35 34 31
31 34 35
34 31 35
32 35 33
34 31 35
33 35 32
34 35 31
34 33 33
34 34 32
34 33 33
32 33 35
32 34 34
35 33 32
35 32 33
34 34 32
32 35 33
34 33 33
33 35 32
33 35 32
33 35 32
35 31 34
34 33 33
33 33 34
34 31 35
34 35 31
34 31 35
32 34 34
35 33 32
35 32 33
35 34 31
34 32 34
32 34 34
35 32 33
33 34 33
32 33 35
35 31 34
34 35 31
33 35 32
0 0 0
样例输出:
100.000%
69.231%
64.981%
80.864%
72.400%
69.287%
94.322%
范围约定:
对于\(40\%\)的测试点,\(n\leq 500\)
对于\(100\%\)的测试点,\(n\leq 1000,m_1,m_2\leq 100\)
分析:
游戏按轮进行。
对于每一轮,不妨设\(A\)赢的概率为\(p_1\),\(B\)赢的概率为\(p_0\),如何计算最终\(A\)获胜的概率呢?
不妨设\(f(x)\)为当\(A\)比\(B\)多赢\(x\)轮时,\(A\)最终获胜的概率(为了方便,如果\(B\)比\(A\)多赢\(y\)轮,我们认为\(A\)比\(B\)多赢\(-y\)轮)
当\(A\)比\(B\)多赢\(x\)轮时,
有\(p_1\)的概率进入\(A\)比\(B\)多赢\(x+1\)轮的状态。
有\(p_0\)的概率进入\(A\)比\(B\)多赢\(x-1\)轮的状态。
有\((1-p_0-p_1)\)的概率停留在\(A\)比\(B\)多赢\(x\)轮的状态。
若\(p_1=0\),则\(A\)获胜概率为\(0\)。
当\(p_1\neq0\)时,根据全概率公式及题意,得到如下递推式及初始状态
这是一个常系数齐次差分方程,通过求解特征方程,算出通解后代入初始条件解任意常数,最终可以解得满足初始条件的特解为
求解过程:
将递推式化为
\[p_1f(x+1)-(p_0+p_1)f(x)+p_0f(x-1)=0 \]其对应的特征方程为
\[p_1\lambda^2-(p_0+p_1)\lambda+p_0=0 \]因式分解得
\[(p_1\lambda-p_0)(\lambda-1)=0 \]解得
\[\lambda_1=\frac{p_0}{p_1}\\ \lambda_2=1 \]若\(p_0=p_1\),则通解为
\[f(x)=C_1+C_2x \]由于\(f(m_1)=1,f(-m_2)=0\),故
\[\left\{\begin{aligned} C_1+C_2m_1=1\\ C_1-C_2m_2=0 \end{aligned}\right. \]解得
\[C_1=\frac{m_2}{m_1+m_2}\\ C_2=\frac{1}{m_1+m_2}\\ \]故此时
\[f(x)=\frac{x+m_2}{m_1+m_2} \]若\(p_0\neq p_1\),则通解为
\[f(x)=C_1\left(\frac{p_0}{p_1}\right)^x+C_2 \]由于\(f(m_1)=1,f(-m_2)=0\),故
\[\left\{\begin{aligned} C_1\left(\frac{p_0}{p_1}\right)^{m_1}+C_2=1\\ C_1\left(\frac{p_0}{p_1}\right)^{-m_2}+C_2=0 \end{aligned}\right. \]解得
\[C_1=\frac{(\frac{p_0}{p_1})^{m_2}}{(\frac{p_0}{p_1})^{m_1+m_2}-1}\\ C_2=-\frac{1}{(\frac{p_0}{p_1})^{m_1+m_2}-1} \]故此时
\[f(x)=\frac{(\frac{p_0}{p_1})^{x+m_2}-1}{(\frac{p_0}{p_1})^{m_1+m_2}-1} \]
所以
所以最终获胜的概率是关于\(p_0/p_1\)的一个函数,不妨设这个函数为\(g(x)\)
通过求导(高中数学常见的导数题证明),可以发现这个函数单调递减,所以要使得\(g(x)\)最大,就需要让\(x\)尽量小。
简略证明:
\[\lim\limits_{x\rightarrow1}g(x)=\lim\limits_{x\rightarrow1}\frac{m_2\ln x}{(m_1+m_2)\ln x}=\frac{m_2}{m_1+m_2}=g(1) \]所以\(g(x)\)在\(x=1\)处连续。
令\(a=m_2,b=m_1+m_2\),则有\(1\leq a<b\)
\[g(x)=\frac{x^a-1}{x^b-1} \]求导得
\[g'(x)=\frac{x^{a-1}[(a-b)x^b+bx^{b-a}-a]}{(x^b-1)^2} \]设\(h(x)=(a-b)x^b+bx^{b-a}-a\),则有
\[h'(x)=b(a-b)x^{b-1}+b(b-a)x^{b-a-1}\\ =-bx^{b-a-1}(b-a)(x^a-1) \]可以得出\(h(x)\)在\((0,1)\)上递增,\((1,+\infty)\)上递减,
而\(h(1)=0\),所以\(h(x)\leq 0\),从而\(g'(x)\leq 0\),所以\(g(x)\)在\((0,1)\)和\((1,+\infty)\)上分别递减。
又有\(g(x)\)在\(x=1\)处连续,所以有\(g(x)\)在\((0,+\infty)\)上递减。
所以我们要求出\(p_0/p_1\)的最小值。
如果给定这\(n\)局的出拳策略,则可以很容易地在\(O(n^2)\)内使用动态规划的算法算出\(p_0\)和\(p_1\),具体做法就是设\(f(i,j)\)为进行\(i\)局结束后,\(A\)获胜数减\(B\)获胜数为\(j\)的概率,容易写出状态转移方程。
问题是现在要使得\(p_0/p_1\)最小,我们不可能直接枚举策略。
想到设\(f(i,j)\)为已经进行到第\(i\)局结束,此时\(A\)获胜数减\(B\)获胜数为\(j\)这个状态的最小的\(p_0/p_1\),我们发现难以下手写转移方程。
注意到目标函数是一个分式,我们不妨考虑进行分数规划,整体的想法就是二分+可行性判断。
由于最小值可能非常大不便于二分,所以我们尝试令最小值\(ans=s/(1-s)(0\leq s<1)\)(构造一个双射将\([0,+\infty)\)映射到\([0,1)\))。
这样只需要二分\(s\)即可,认为\(s\)为此时\(s\)的二分值。
如果存在一种出拳策略,使得\(p_0/p_1<s/(1-s)\),则\(s/(1-s)>ans\),反之\(s/(1-s)\leq ans\)。
其等价于,若\((sp_1+(s-1)p_0)_{\max}>0\),则\(s>ans/(1+ans)\)(即\(s\)偏大,需要缩小),反之\(s\leq ans/(1+ans)\)(即\(s\)偏小,需要放大)。
现在问题就转化成了求\(sp_1+(s-1)p_0\)的最大值,显然,这是需要用动态规划解决的。
设\(f(i,j)\)为,已知进行到第\(i\)局结束时,\(A\)获胜数减\(B\)获胜数为\(j\),最大的\(sp_1+(s-1)p_0\),由于\(sp_1+(s-1)p_0\)是关于概率\(p_1\),\(p_0\)的线性组合,所以根据概率乘法公式状态转移比较容易写出。
(注:\(k=0,1,2\)分别表示\(A\)这局的策略为剪刀,石头,布,\(p(i,0),p(i,1),p(i,2)\)分别表示\(B\)第\(i+1\)局出石头,布,剪刀的概率)
初始状态
代码:
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
int n, m1, m2;
const int maxn = 1010;
const double eps = 1e-8;
int p[maxn][3];
double f[maxn][maxn];
bool check(double s) {
for (int i = 0; i < n; i++) {
f[n][i] = s - 1;
}
f[n][n] = 0;
for (int i = n + 1; i <= 2 * n; i++) {
f[n][i] = s;
}
for (int i = n - 1; i >= 0; i--) {
for (int j = n - i; j <= n + i; j++) {
f[i][j] = (f[i + 1][j - 1] * p[i][0] + f[i + 1][j] * p[i][2] +
f[i + 1][j + 1] * p[i][1]) /
100;
for (int k = 1; k < 3; k++) {
f[i][j] = max(f[i][j], (f[i + 1][j - 1] * p[i][k] +
f[i + 1][j] * p[i][(k + 2) % 3] +
f[i + 1][j + 1] * p[i][(k + 1) % 3]) /
100);
}
}
}
return f[0][n] > eps;
}
void solve() {
double l = 0, r = 1;
bool flag;
while (r - l > eps) {
double mid = (l + r) / 2;
flag = check(mid);
if (flag) {
r = mid;
} else {
l = mid;
}
}
if (l - 0.5 < eps && l - 0.5 > -eps) {
printf("%.3lf%%\n", (double)m2 / (m1 + m2) * 100);
} else {
double ans = l / (1 - l);
printf("%.3lf%%\n", (pow(ans, m2) - 1) / (pow(ans, m1 + m2) - 1) * 100);
}
}
int main() {
while (scanf("%d%d%d", &n, &m1, &m2) == 3 &&
!(n == 0 && m1 == 0 && m2 == 0)) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < 3; j++) {
scanf("%d", &p[i][j]);
}
}
solve();
}
return 0;
}