取球
Portal --> ???(这是一道。。没有来源的题==)
Description
有一个透明的袋子,里面有\(R\)个红球\(B\)个蓝球,两种球除了颜色以外没有任何区别,一开始会先随机从袋子里面取走\(m\)个球,球的颜色只有游戏结束之后才知道,接着两个人轮流从袋子里面取球,球的颜色只有取出来之后才知道,每次取球数量在\(1\sim 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\n",f[R][B]);
}