Codeforces 235B Let's Play Osu!

問題

n種類のコインを投げて、出た面を順番に記録する。
i番目のコインが表の確率はp[i]である。

このとき、連続する○の大きさの二乗の総和
(たとえば○○×○××だったら、2^2 + 1^2 = 5)の期待値を求めよ。

制約条件

n≦10^5
0≦p≦1

方針

漸化式を立てて計算できないか考える。
i番目までコインを投げたときの列、i=2だとしたら
○○
○×
×○
××
がありえて、それぞれの後ろに○または×が付きうるので、
期待値の計算の一部が再利用できそう。


具体的には期待値から、最後のブロックの寄与分を引いて新たな寄与分を足せばよい。
最後のブロックの寄与分
i枚コインを投げたとき、最後のブロックの大きさがjである確率をP[i, j]とすれば、
ΣP[i, j] * j^2
これは、ΣP[i, j] * jを計算して保持しておくことで簡単に計算できる。


すなわち、ΣP[i+1, j] * j^2 = ΣP[i, j] * (j+1)^2 * (次のコインが表の確率)
また、ΣP[i+1, j] * j = ΣP[i, j] * (j+1) * (次のコインが表の確率)
ここで、ΣP[i, j] = 1である。


これを使って期待値の漸化式を立てる。
まず、最後に×がくる場合による増減が、Ei * (1 - p[i])
次に新たにできるブロックの寄与分による増減は、ΣP[i+1, j]j^2


Eiの期待値のうち、Ei+1に残る部分は、
後ろのブロックを削り取ったあと、更に○が出たときだけ残るので、
(Ei - ΣP[i, j] * j^2) * p[i]


これらを足し合わせればよい。
editorialはもっときれいなことをやっているぽい。

ソースコード

int n;
double p[100010], a, b, c, d, ans;

int main(){
	cin >> n;
	rep(i, n) cin >> p[i];
	rep(i, n){
		d = (b + 1) * p[i];
		c = (a + 2 * b + 1) * p[i];
		
		ans = ans * (1 - p[i]) + c + (ans - a) * p[i];
		a = c; b = d;
	}
	printf("%.9f\n", ans);
	
	return 0;
}