题目链接:BZOJ-4318 OSU!
题目
Description
osu 是一款群众喜闻乐见的休闲软件。
我们可以把osu的规则简化与改编成以下的样子:
一共有n次操作,每次操作只有成功与失败之分,成功对应1,失败对应0,n次操作对应为1个长度为n的01串。在这个串中连续的 X个1可以贡献X^3 的分数,这x个1不能被其他连续的1所包含(也就是极长的一串1,具体见样例解释)
现在给出n,以及每个操作的成功率,请你输出期望分数,输出四舍五入后保留1位小数。
Input
第一行有一个正整数n,表示操作个数。接下去n行每行有一个[0,1]之间的实数,表示每个操作的成功率。
Output
只有一个实数,表示答案。答案四舍五入后保留1位小数。
Sample Input
3
0.5
0.5
0.5
Sample Output
6.0
HINT
【样例说明】
000分数为0,001分数为1,010分数为1,100分数为1,101分数为2,110分数为8,011分数为8,111分数为27,总和为48,期望为48/8=6.0
N<=100000
解析
codeforces上有一道很像的题目:Let’s Play Osu!。那道题目里连续
1
1
1产生的是长度平方和收益,比这个要稍微简单一些,不过了解了思路后都是一样的。
首先,关于立方和。其实很简单啦,要是要求的是平方和,那我们可以利用
(
x
+
1
)
2
=
x
2
+
2
∗
x
+
1
(x+1)^2=x^2+2*x+1
(x+1)2=x2+2∗x+1实现递推,类似的,立方和则是用
(
x
+
1
)
3
=
x
3
+
3
∗
x
2
+
3
∗
x
+
1
(x+1)^3=x^3+3*x^2+3*x+1
(x+1)3=x3+3∗x2+3∗x+1实现递推。关键在于每次实际上只有最后一段连续
1
1
1长度是变化的,直接这样递推会导致前面的连续段也出现改变。怎么办呢?
拿到题目后一个比较朴素的想法:开一个
n
2
n^2
n2的数组
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]记录推到第
i
i
i位,最后一个连续段长度为
j
j
j的期望分数。但是
n
n
n的范围没有那么友好,那我们只能O(n)解决了。我们仔细想一下,既然我们递推过程缺少的是最后一个连续段的长度,那我们直接开O(n)的数组维护最后一个连续段的长度期望不就可以了吗?
所以我们可以开两个辅助数组维护以
i
i
i号元素结尾的最长段及其平方的期望(平方的期望不等于期望的平方
E
(
x
2
)
−
E
2
(
x
)
=
D
(
x
)
E(x^2)-E^2(x)=D(x)
E(x2)−E2(x)=D(x),D是方差),然后每一位都分0、1两种情况,于是有公式
d
p
[
i
+
1
]
=
(
1
−
p
[
i
]
)
∗
d
p
[
i
−
1
]
+
p
[
i
]
∗
(
d
p
[
i
−
1
]
+
3
∗
t
p
[
i
−
1
]
+
3
∗
f
p
[
i
−
1
]
+
1
)
dp[i+1]=(1-p[i])*dp[i-1]+p[i]*(dp[i-1]+3*tp[i-1]+3*fp[i-1]+1)
dp[i+1]=(1−p[i])∗dp[i−1]+p[i]∗(dp[i−1]+3∗tp[i−1]+3∗fp[i−1]+1),剩下的就是敲出来。
上代码:
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 10;
double p[maxn], dp[maxn], tp[maxn], fp[maxn];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%lf", &p[i]);
dp[0] = fp[0] = tp[0] = 0;
for(int i = 1; i <= n; i++){
fp[i] = (fp[i-1]+1)*p[i];
tp[i] = (tp[i-1]+2*fp[i-1]+1)*p[i];
dp[i] = (1-p[i])*dp[i-1]+p[i]*(dp[i-1]+3*tp[i-1]+3*fp[i-1]+1);
}
printf("%.1f\n", dp[n]);
}