LG-P5249 [LnOI2019]加特林轮盘赌 题解
LG-P5249 [LnOI2019]加特林轮盘赌 Solution
更好的阅读体验戳此进入
题面
存在加特林,存在 $ n $ 个人围城一圈轮流使用加特林并每次均有 $ P $ 的概率被打死,求第 $ k $ 个人是最终唯一的幸存者的概率。
Solution
很有意思的一道题,和一般的朴素概率 DP 不尽相同。
前面的思路和其它题解差不多,主要细说一下最后推式子的步骤。
对于 $ n = 1 $ 平凡解决,对于 $ n = 2 $ 考虑,发现不能用一般的线性递推的思路,不难想到对于处于第一位的人,若其没有被打死,那么下一次的时候枪指向下一个人,局面与初始时原来处于第一位的人处于第二位等效,那么不难想到我们令 $ dp(2, i) $ 表示 $ 2 $ 个人时处于第 $ i $ 位的人幸存的概率,不难发现转移:
也就是说考虑其原本处于第 $ 1 $ 位,被打死则无贡献,没有被打死并且在下一次被移动到 $ 2 $ 位置后仍然幸存即为答案。
写到这就不难发现这东西是存在后效性的,或者说不存在初值,但是仍然存在显然的 $ dp(2, 1) + dp(2, 2) = 1 $,所以也可以计算。
于是考虑扩展到 $ dp(n, i) $,不难想到最朴素地:
此时我们仔细想一下刚才的过程,不难发现意义就是我们每次只崩首位的人,崩完之后不移动枪,而是将整个圆排列对应旋转。
然后我们考虑对于中间的转移,不难想到对于 $ dp(n, k) $,我们如果 $ P $ 的概率崩掉首位了,则 $ k \leftarrow k - 1, n \leftarrow n - 1 $,即 $ P \times dp(n - 1, k - 1) $,如果 $ 1 - P $ 地没崩掉,那么人虽然没有减少但是圆排列仍然需要转一下 $ k \leftarrow k - 1 $,也就是 $ (1 - P) \times dp(n, k - 1) $,于是转移即为:
当然我们这东西是没有初值的,不能直接递推,但是共存在 $ n $ 个方程,可以直接解 $ n $ 元 $ 1 $ 次方程组,用高斯消元即可,但是这个是 $ O(n^4) $ 的,考虑优化。
考虑对于 $ P \times dp(n - 1, k - 1) $ 此时已经为常量了,令其为 $ \xi $,则有:
令 $ f(k) = dp(n, k) $,令 $ \xi(k) $ 表示 $ dp(n - 1, k - 1) \times P \times (1 - P)^{k - 1} $,则有:
这样我们可以用 $ f(1) $ 表示出所有 $ f(k) $,然后带入 $ \sum f(k) = 1 $ 求出 $ f(1) $,再 $ O(n) $ 求出所有的 $ f(k) $。
注意需要特判 $ P = 0 $ 的情况,且注意需要滚动数组优化空间。
最终时间复杂度 $ O(n^2) $,空间复杂度 $ O(n) $。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define EPS (1e-10)
template < typename T = int >
inline T read(void);
ld P;
int N, K;
ld dp[2][11000];
int main(){
scanf("%Lf", &P), N = read(), K = read();
if(P < EPS)printf("%.10Lf\n", N == 1 ? (ld)1 : (ld)0), exit(0);
dp[1][1] = 1.0;
bool cur(false);
for(int i = 2; i <= N; ++i){
ld K(1.0), B(0.0), base1(1.0), base2(0.0);
for(int j = 2; j <= i; ++j)
base1 *= (1 - P), base2 = base2 * (1 - P) + P * dp[cur ^ 1][j - 1],
K += base1, B += base2;
dp[cur][1] = (1 - B) / K;
for(int j = 2; j <= i; ++j)
dp[cur][j] = (1 - P) * dp[cur][j - 1] + P * dp[cur ^ 1][j - 1];
cur ^= 1;
}printf("%.10Lf\n", dp[cur ^ 1][K]);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2023_02_25 初稿