概率/期望 DP Let‘s Play Osu!
题目链接:Problem - B - Codeforces
题目大意:
有一个长度为 n 的序列,第 i 个位置上为 "O" 的概率为 ,为 "X" 的概率为
。
一个序列的得分定义为:
求整个序列的期望得分。
Solution1:
注意到一个关键性质:(x + 1) ^ 2 - x^2 = 2x + 1
也就是说,我们在已经有一段长度为 x 的连续 "O" 序列的情况下,再加多一个 "O" ,得到的贡献就会加上 "2x+1" 。
于是就把平方的期望转化成了线性的。
可以设一个 len[i] 表示以 i 结尾的连续 "O" 序列的期望长度,f[i] 表示序列1~ i 的期望贡献,那么:
f[i] = (1-p[i]) * f[i-1] + p[i] * (f[i-1] + 2 * len[i-1] + 1)
len[i] = p[i] * (len[i-1] + 1) + (1-p[i]) * 0 = p[i] * (len[i-1] + 1)
Code:
#include<cstdio>
#include<cstring>
using namespace std;#define N 100005int n;
double p[N],len[N],f[N];int main()
{scanf("%d",&n);for (int i = 1;i <= n;++ i) scanf("%lf",&p[i]);len[0] = f[0] = 0.00;for (int i = 1;i <= n;++ i){f[i] = (1.00 - p[i]) * f[i - 1] + p[i] * (f[i - 1] + 2.00 * len[i - 1] + 1.00);len[i] = p[i] * (len[i - 1] + 1.00);}printf("%.6lf\n",f[n]);return 0;
}
Solution2:
Solution1是最为广泛的做法,而官方解答里还有一个更为巧妙的发现。
注意到另一个关键性质:
也就是说,对于一段长度为 n 的连续 "O" 序列,得分贡献可以拆分为两部分:
1. 每一个 "O" 贡献 1 分
2. 每一对 "OO" 贡献 2 分 (不一定要相邻)
于是我们又可以利用期望的线性性质拆分来做。
第一部分的答案很简单,就是计算整个序列期望出现多少次 "O" ,即 。
第二部分的答案,我们要计算的就是 [i,j] 这段序列出现全 "O" 的概率,记作 P(i,j) ,其实 P(i,j) 代表的就是第 i 个为 "O" 和第 j 个为 "O" 的组合贡献,它并不包含譬如 P(i+1,j)、P(i,j + 1) ... 的贡献。那么这部分的答案就是 。下面考虑如何快速计算 P(i,j):
(注意我们不可以用类似前缀和的想法维护前缀积,那样精度会爆掉)
可以设 ,则
那么两部分答案加起来就是:
Code:
#include<cstdio>
#include<cstring>
using namespace std;#define N 100005int n;
double p[N],f[N],ans;int main()
{scanf("%d",&n),ans = 0.00;for (int i = 1;i <= n;++ i) scanf("%lf",&p[i]),ans += p[i];f[1] = 0.00;for (int i= 2;i <= n;++ i) f[i] = f[i - 1] * p[i] + p[i - 1] * p[i],ans += 2.00 * f[i];printf("%.6lf\n",ans);return 0;
}
Solution2 看似比较难想,但它给了我们一种做题的全新思路。