Portal --> ???(这是一道。。没有来源的题==)
Description
有一个透明的袋子,里面有(R)个红球(B)个蓝球,两种球除了颜色以外没有任何区别,一开始会先随机从袋子里面取走(m)个球,球的颜色只有游戏结束之后才知道,接着两个人轮流从袋子里面取球,球的颜色只有取出来之后才知道,每次取球数量在(1sim min(n,)剩余球数())之间,取出最后一个红球的人输,求两人都采取最优策略的情况下先手获胜概率
数据范围:(1<=R,B<=100,1<=n<=10,0<=m<=R-1)
Solution
(这题其实是特别暴力地将所有的局面的概率dp出来。。嗯。。但是因为我推的式子比较麻烦所以写的题解有点长。。应该有一些。。更加简便的方法)
化简一下条件,考虑输的情况总共有两种:
1、取完(m)个球之后没有红球剩下(然而实际上因为数据范围的限制这种情况是不会发生的qwq)
2、决策完之后没有红球剩下
上面两个都与“是否有红球剩下”有关,所以考虑将这个东西的概率表示出来
记(g[r][b][m])表示从有(r)个红球,(b)个蓝球的袋子里面取(m)个球出来后,袋子里面还有红球的概率(具体一点就是假设我取出来的(m)个球的颜色分布为(k)个红球,(m-k)个蓝球,(k<r)的概率)
那么可以得到:
看一下这个“先随机取出(m)个球“的操作,我们考虑将这个问题稍微转化一下:假设已经钦定了一个取球序列(包括顺序和博弈过程),那么“从袋子里取球”的操作其实就相当于从序列头取球,那么一开始拿走(m)个球对应的就是序列的前(m)项,最终的答案应该是所有可能产生的序列得到的先手胜的数量之和/序列数量之和
我们考虑将每一个可能产生的序列的前(m)项都移到最后面去,能够得到的新的序列集合是与原来的序列集合一样的,每种情况的出现概率也相同,这个时候就变成了“两人轮流取球,最后剩(m)个球的时候停止”,也就是说我们可以将这个取(m)个球的操作放到最后,顺序并不影响结果,所以这个时候我们就可以直接从后面的局面转移了
用(f[r][b])表示袋子里面有(r)个红球,(b)个蓝球,取走(m)个未知的球之后的先手的胜率
可以得到:
其中(1<=i<=n)
具体一点就是:
枚举当前的决策,(i)表示取的总球数,(j)是表示取出来的红球的个数
因为是要采取最优策略所以是所有的情况中取(max)
然后前面的组合数表示的是(i)个球中颜色分布为(j)个红球(i-j)个蓝球的概率
然后(1-f[r-j][b-(i-j)])表示的是下一个人面对这样的局面并且失败的概率
最后面的那个是为了保证取完这(i)个球之后,袋子里面还有剩余的红球
具体解释一下最后的那个分数:这是一个条件概率,首先先手没有凉的一个大前提就是取完(m)个球之后还有红球剩余,然后在这个基础上,还要满足在当前决策完了之后,也就是(r-i)个红球(b-(i-j))个蓝球这样的局面下取完(m)个球也有红球剩余
换句话来说就是假设我们已经钦定了最后取的(m)个球中有(k)个红球(下面将满足条件(x)的情况数量记为(cnt(x))),(g[r][b][m])的含义可以理解为(frac{cnt(k<r)}{all}),(g[r-j][b-(i-j)][m])的含义可以理解为(frac{cnt(k<r-j)}{all}),而我们这里需要的概率应该是(frac{cnt(k<r-j)}{cnt(k<r)}),所以就是两个式子相除就好了
那么最后的答案就是(f[R][B]),总的时间复杂度(O(RB(n^2+m)))
写的时候要。。注意一下边界
最后就是。。其实我们会发现(C(200,99))的时候这个组合数会爆long double,然而实际上这个时候它会保留(33)位的有效数字,虽然说并不能够精确地存储整数,但始终可以保证前几位是精确的,那么对于我们这题来说还是足够的(这个时候应该疯狂膜拜sk qwq)
代码大概长这个样子
#include<iostream>
#include<cstring>
#include<cstdio>
#define ldb long double
using namespace std;
ldb g[110][110][110],f[110][110],C[210][210];
ldb sum;
int n,m,R,B;
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%d%d%d%d",&R,&B,&n,&m);
C[0][0]=1;
for (int i=1;i<=200;++i){
C[i][0]=1; C[i][i]=1;
for (int j=1;j<i;++j)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
for (int r=1;r<=R;++r){
for (int b=0;b<=B;++b){
g[r][b][0]=1;
for (int k=1;k<=m&&k<=r+b;++k){
g[r][b][k]=1.0*r/(1.0*(r+b))*g[r-1][b][k-1];
if (b)
g[r][b][k]+=1.0*b/(1.0*(r+b))*g[r][b-1][k-1];
}
}
}
for (int r=1;r<=R;++r){
f[r][0]=0;
for (int b=max(m-r,0);b<=B;++b){
f[r][b]=0;
for (int i=1;i<=n&&i<=r+b;++i){
sum=0;
for (int j=0;j<=i&&j<=r;++j){
if (b<(i-j)) continue;
sum+=C[r][j]*C[b][i-j]/C[r+b][i]*(1-f[r-j][b-(i-j)])*g[r-j][b-(i-j)][m]/g[r][b][m];
}
f[r][b]=max(f[r][b],sum);
}
}
}
printf("%.10Lf
",f[R][B]);
}