[CF148D]Bag of mice[题解]
Bag of mice
- 前言:今天听 \(w\) 老师讲课,思考此题时我给出了和题解不同的做法,但是不知道正确性。下课后花费一个多小时,按照这种做法过掉了这道题目,写一篇题解总结。
题意
给定 \(w\) 只白色老鼠,\(b\) 只黑色老鼠,\(A\) 和 \(B\) 两个人轮流抓取(不放回),先抓到白色老鼠的人赢。
且 \(B\) 每次抓取老鼠后,会从剩下的老鼠 中 随机 跑掉一只。
求 \(A\) 先手获胜的概率。
\(w,b\leq 1000\)
分析
不难发现此题有一个简单性质:如果当前轮次游戏没有结束,则 \(A\) 和 \(B\) 此前抓到的都是黑色老鼠。
考虑枚举游戏进行到第 \(i\) 轮,我们很容易知道之前一共被抓走了 \(i - 1\) 只黑色老鼠,问题的关键在于 \(B\) 选取后随机跑掉的老鼠,如果我们知道这里面跑掉了多少只白色老鼠,很显然我们可以由此推测出当前轮次所有的状态。
所以,在第 \(i\) 轮枚举剩下 \(j\) 只白色老鼠,由于游戏没有结束,则跑掉了 \(w - j\) 只白色老鼠,而一共跑掉了 \((i - 1) / 2\) 只老鼠,所以跑掉了 \((i - 1) / 2 - (w - j)\) 只黑色老鼠。
设 \(x\) 表示第 \(i\) 轮剩余的白色老鼠数量,\(y\) 表示第 \(i\) 轮剩余的黑色老鼠数量,显然,我们有:
考虑如何转移状态。
设 \(f[0][i]\) 表示上一轮次剩余 \(i\) 只白色老鼠的概率,则当在 \(A\) 行动轮次时,我们只需要枚举白色老鼠的数量 \(j\),就可以有:
接下来我们只需要维护 \(f[1][i]\) 即当前轮次结束过后剩余 \(i\) 只白色老鼠的概率。
若为 \(A\) 操作轮次,则我们只需要在每一种概率上乘上选择黑色老鼠的概率 \(\frac{y}{x + y}\)
即可。
若为 \(B\) 操作轮次,则枚举白色老鼠数量 \(j\),显然,如果跑掉了白色老鼠,则有:
即上一轮剩下 \(j\) 只白老鼠的概率乘上 \(B\) 抓取到黑色老鼠的概率再乘上剩余老鼠中放掉白老鼠的概率。
同理,如果跑掉了黑色老鼠,则有
最后只需要注意一些细节和边界即可。
\(code\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
inline int read()
{
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
int w, b, opt;
double f[2][N]; //f[i] 表示还剩下 i 只白鼠的概率
double ans;
int main()
{
w = read(), b = read();
f[1][w] = 1; //初始有 w 只白鼠
for(register int i = 1; ; i++, opt ^= 1){ //枚举游戏轮次
// cout << "轮次: " << i << "\n";
// cout << "上一轮剩下 j 只白鼠的概率:\n";
// for(register int j = 1; j <= w; j++)
// cout << f[opt ^ 1][j] << "\n";
bool flag = false;
for(register int j = 1; j <= w; j++)
if(f[opt ^ 1][j]) flag = true;
if(!flag) break;
if(i & 1){
//A 获胜的概率即选到白鼠的概率
for(register int j = 1; j <= w; j++){ //枚举剩余白鼠的数量
int x = j, y = b - (i - 1) - ((i - 1) / 2 - (w - j));
if(y < 0) continue;
ans = ans + f[opt ^ 1][j] * ((double)x / (double)(x + y));
}
}
if(!(i & 1)){
for(register int j = 1; j <= w; j++){ //枚举剩余白鼠的数量
int x = j, y = b - (i - 1) - ((i - 1) / 2 - (w - j));
if(y < 1) continue;
//计算本轮还没有进行前,剩余白鼠为 j,则剩余黑鼠的数量
/*剩余 j 只白鼠时,由于进行了 i - 1 轮游戏,被抓走了 i - 1 只,且前 i - 1 轮游戏
B 放跑了 (i - 1) / 2 下取整只老鼠,由于走掉的白鼠即 w - j 一定是被放跑的,所以放跑
的黑鼠数量为 i / 2 - (w - j)。
*/
//本轮放跑了一只白鼠
// cout << j << " " << x << " " << y << "\n";
f[opt][j - 1] += f[opt ^ 1][j] * ((double)y / (double)(x + y)) * ((double)x / (double)(x + y - 1)); //枚举本轮放跑的白鼠的数量
//cout << f[opt ^ 1][j] << "\n";
//cout << y << " " << x + y << " " << y - 1 << " " << x + y - 1 << "\n";
f[opt][j] += f[opt ^ 1][j] * ((double)y / (double)(x + y)) * ((double)(y - 1) / (double)(x + y - 1));
//cout << f[opt][j] << "\n";
//乘上被抓黑色老鼠的概率
}
}
else{ //不会放跑老鼠
for(register int j = 1; j <= w; j++){
int x = j, y = b - (i - 1) - ((i - 1) / 2 - (w - j));
if(y < 0) continue;
f[opt][j] = f[opt ^ 1][j] * ((double)y / (double)(x + y)), f[opt ^ 1][j] = 0;
}
}
}
printf("%.10lf\n", ans);
return 0;
}