●BZOJ 4318 OSU!

*ZJ發表於2018-03-11

題鏈:

http://www.lydsy.com/JudgeOnline/problem.php?id=4318
題解:

期望dp
如果我們能夠得到以每個位置結尾形成的連續1的長度的相關期望,那麼問題就好解決了。

定義g[i]表示以1位置結尾的連續1的長度的期望。
轉移顯然:g[i]=p[i]*(g[i]+1)
然後定義h[i]表示以1位置結尾的連續1的長度的平方的期望
由於(x+1)^2=x^2+2x+1,
所以h[i]=p[i]*(h[i-1]+2*g[i-1]+1)

最後定義f[i]表示1~i這個區間期望能得到的分數,
分為此時i位置得到1和得到0兩種情況:
得到1,由於(x+1)^3=x^3+3*x^2+3x+1 那麼貢獻為:p[i]*(f[i-1]+3*h[i-1]+3*g[i-1]+1)
得到0,那麼直接為前面的期望得分,貢獻為(1-p[i])*f[i-1]
所以f[i]的轉移為:f[i]=(得到1)p[i]*(f[i-1]+3*h[i-1]+3*g[i-1]+1)+(得到0)(1-p)*f[i-1];

.....................................................................

==,難道沒有感覺這個f[i]的轉移有一絲絲詭異麼?
先看看這個錯的做法,
多了一個d[i],表示以i結尾形成的連續1的長度的3次方的期望。
那麼其轉移類似g和h的轉移:
d[i]=p[i]*(d[i-1]+3*h[i-1]+3*g[i-1]+1)
然後再去求得f[i],同樣地分為當前第i位得到1和得到0兩種情況:
f[i]=(得到1)d[i]+(得到0)(1-p[i])*f[i-1]

乍一看似乎沒問題,但是在(得到1)那裡卻出了問題:
f[i]表示的是1~i這個區間期望能夠得到的分數,
但是在(得到1)這個轉移這裡,我們卻只考慮了以i結尾的期望的那段1的貢獻,然而其它部分的貢獻就沒有轉移過來。
這也就是這個做法得到的答案比正確答案小的原因。
(可以強行把之前的貢獻再加進來麼?233,我反正加不來。。。)

.......................................................................

現在再反過來看看之前正確的f[i]的求法(沒有d[i]陣列的那個做法)
f[i]=(得到1)p[i]*(f[i-1]+3*h[i-1]+3*g[i-1]+1)+(得到0)(1-p)*f[i-1];

顯然(得到0)的那個轉移沒有問題。

那麼我們來想想(得到1)的那麼那個轉移是如何解決掉那個錯誤做法出現的問題的。
由於f[i-1]表示的是區間1~i-1的期望得分,
那麼我們就可以把f[i-1]看成是由兩個部分組成的:
一個部分是以i-1結尾的期望的那段連續的1造成的貢獻A(一個長度的3次方的期望),另一部分則是其它部分的貢獻B:
所以(得到1)這個轉移可以看成是:p[i]*(B+A+3*h[i-1]+3*g[i-1]+1),
顯然,後面的A+3*h[i-1]+3*g[i-1]+1計算的就是以i結尾形成的連續1的長度的3次方的期望,
而B則是其它部分的貢獻。
所以就是這樣巧妙地把新的貢獻和其它部分的貢獻都統計進了f[i]裡面。

以上就是個人的見解。


程式碼:

 

#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
double g[MAXN],h[MAXN],f[MAXN],p;
int N;
int main(){
	ios::sync_with_stdio(0);
	cin>>N;
	for(int i=1;i<=N;i++){
		cin>>p;
		g[i]=p*(g[i-1]+1);
		h[i]=p*(h[i-1]+2*g[i-1]+1);
		f[i]=p*(f[i-1]+3*h[i-1]+3*g[i-1]+1)+(1-p)*f[i-1];
	}
	cout<<fixed<<setprecision(1)<<f[N]<<endl;
	return 0;
}