洛谷 P5249 [LnOI2019]加特林轮盘赌 概率DP
设 \(f[n][k]\) 为 \(n\) 个人第 \(k\) 个人胜利的概率。初始化 \(f[n][1]=1\)
第一个人要想赢的话第一枪不能中,然后他就相当于成了 \(n\) 个人里的第 \(n\) 个,\(f[n][1]=(1-P_0) \times f[n][n]\)
对于其他人,如果第一个人中了,就转移到了 \(f[n-1][k-1]\),倘若没中,就转移到了 \(f[n][k-1]\),\(f[n][k]=P_0 \times f[n-1][k-1] + (1-P_0) \times f[n][k-1]\)
另外还有\(\displaystyle \sum_{i=1}^n f[n][i]=1\)
手动高斯消元即可,具体方法可以看这
#include<iostream>
#include<cstdio>
#define DB double
using namespace std;
int n, k, now = 1, last;
DB p0, a, c, sa, C;
DB f[2][20010];
int main()
{
cin >> p0 >> n >> k;
if (p0 == 0)return puts(n == 1 ? "1" : "0") == 2333;
f[1][1] = 1;
for (int i = 2; i <= n; ++i)
{
a = 1; c = sa = C = 0; last = now, now ^= 1;
for (int j = 2; j <= i; ++j)a *= (1 - p0), sa += a, c = (1 - p0) * c + p0 * f[last][j - 1], C += c;
f[now][1] = (1 - C) / (sa + 1);
for (int j = 2; j <= i; ++j)f[now][j] = p0 * f[last][j - 1] + (1 - p0) * f[now][j - 1];
}
printf("%.10f", f[now][k]);
return 0;
}
...是不是有点迷糊?,因为这题是实数输出,我们可以模拟的,次数一定后误差就会小到忽略不计(其他概率题也可以试试这种方法)
具体方法就是枚举这个人在每一轮取胜的概率(枚举到 \(10^5\) 就够了),最后加起来即可。
附上treAKer巨佬的主函数。
int main()
{
std::cin >> p; n = read(); k = read();
if(n == 1) return puts("1"), 0;
for(int i = 1; i <= 1e5; ++ i) s[i] = 1 - ksm(1-p,i);
for(int i = 1; i <= 1e5; ++ i) a[i] = ksm(s[i], k - 1), b[i] = ksm(s[i], n - k);
for(int i = 1; i <= 1e5; ++ i)
{
if(k == n) ans += a[i] * (s[i] - s[i - 1]);
else if(k == 1) ans += b[i - 1] * (s[i] - s[i - 1]);
else ans += a[i] * b[i - 1] * (s[i] - s[i - 1]);
}
printf("%0.9f",ans);
return 0;
}