洛谷 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;
}
posted @ 2020-03-30 21:20  wljss  阅读(85)  评论(0编辑  收藏  举报