●Joyoi 綠豆蛙的歸宿
題鏈:
http://www.joyoi.cn/problem/tyvj-1933
題解:
期望dp,拓撲序
定義dp[i]表示從i點到N點的期望距離。
令cnt[u]表示u的出度。
顯然$$dp[u]=\sum_{u->v}(dp[v]+e(邊權))* \frac{1}{cnt[u]}$$
由於是個DAG,所以拓撲排序後,從後向前dp即可。
題外話:為何不能正向dp:(希望聰明可愛的泥萌能看懂接下來的分析)
好吧,其實正向dp是可以的,但是沒有反向dp來的直接和簡單。
我們來看看弱弱的我當初覺得應該怎樣去正向dp:
定義dp[i]表示從1點到i點的期望距離。
那麽我們仿照之前反向dp的思路,我們找到當前v點的來源點u,
顯然dp[v]肯定和dp[u]以及u的信息有關。
那u對v的貢獻是不是就直接是$$dp[v]+=\frac{dp[u]+e(邊權)}{cnt[u]}$$
即我們希望上式得到:
1到v的期望距離dp[v] = (1到u的期望距離 + u到v的邊權)*(u到v的概率) [u為所有能到v的點]
為檢驗是否正確,我就試了幾個小例子:
1.一個點沒有邊,答案為0,正確誒!(...)
2.兩個點,一條邊:1→ 2:3(箭頭兩頭為邊的起點和終點,3為邊權),答案為3,又正確了!(廢話)
3.三個點,3條邊:1→ 2:1,1→ 3:3,2→ 3:2,用腳趾頭也算得出來答案為3,可是程序喜聞樂見地輸出了一個4。
???怎麽回事,dp哪裏出現的問題?
經過一番分析,我找到了問題的根源,就處在dp轉移時加的邊權e那裏。
在詳細說明問題之前,我們先來看看通常倒著做的期望dp裏面的各個狀態的期望是如何實現遞推的,
即一個狀態j的期望是如何通過計算的到其前繼狀態i的期望的。
假設狀態i轉移到狀態j的概率為p,代價為w,
用E(j)表示從狀態j到結束的期望,E(i)表示從狀態i到結束的期望,E(i→ j)表示狀態i下一步必須為狀態j,再到結束的期望。
由期望的定義可以知道:
E(j)=p1*w1+p2*w2+p3*w3+...+pn*wn(即第一種情況的概率*權值+第二種情況的概率*權值+...)
然後如果欽定i狀態必須轉移到j的話(代價為w),不那發現,
E(i→ j)=p1*(w1+w)+p2*(w2+w)+p3*(w3+w)+...+pn*(wn+w)
註意到了麽,從i→ j這個狀態到結束的情況數沒有變化,每種情況的概率也沒變,唯一的變化只是每種情況的權值都加了w。
而通常倒著做的dp,我們定義的狀態往往是表示從該狀態出發到結束的期望,即把該狀態看成了子問題的起點。
所以E(j)中所有的概率之和p1+p2+p3+...+pn = 1
那麽:E(i→ j)=p1*(w1+w)+p2*(w2+w)+p3*(w3+w)+...+pn*(wn+w)
=p1*w1+p2*w2+p3*w3+...+pn*wn + p1*w+p2*w+p3*w+..+pn*w
=E(j)+w
然後再把這個所謂的"欽定"改為"有p的概率從狀態i到狀態j",並累加進E(i):
E(i)+=p*E(i→ j)即E(i)+=p*(E(j)+w)
本題我們反向dp的轉移就是這麽推出來的。
那麽回到之前的問題,為何那樣正向dp就出錯了:
同樣地,我們設當前在i點,其前繼狀態為j點,從j轉移到i有p的概率,代價為w,
用E(j)表示從起點到j點的期望距離,E(i)表示從起點到i點的期望距離,E(j→ i)表示i點由j點而來的情況下,從起點到i點的期望距離。
我們用期望定義來寫出E(j)的構成:
E(j)=p1*w1+p2*w2+p2*w3+...+pn*wn
然後類似上面的步驟,我們欽定i狀態必須由j狀態轉移而來:
E(j→ i)=p1*(w1+w)+p2*(w2+w)+p3*(w3+w)+...+pn*(wn+w)
當然這裏還沒有任何問題,同樣的是情況數沒有變化,每種情況的概率也沒變,唯一的變化只是每種情況的權值都加了w。
我們繼續,嘗試化簡E(j→ i):
E(j→ i)=p1*(w1+w)+p2*(w2+w)+p3*(w3+w)+...+pn*(wn+w)
=p1*w1+p2*w2+p3*w3+...+pn*wn + p1*w+p2*w+p3*w+..+pn*w
=E(j)+p1*w+p2*w+p3*w+..+pn*w
然後上式等於E(j)+w麽?
問題就出在這裏。
當然不,因為這個dp定義下,每個dp狀態的起點都是1號點,也就是說,
E(j)裏面每種情況的概率應該是:第一種從1點到j的情況的概率p1,第二種從1點到j點的情況的概率p2,第三種p3...pn
但是顯然1點不會只到j點,(好吧除了之前搞笑用的第一組和第二組測試數據)
所以g(j) = p1+p2+p3+...+pn ≠ 1,
那麽順理成章地可以得到 E(j→ i) ≠ E(j) + w
也就可以得到,沒有所謂的"欽定"而是換成概率p,E(i)也不能加上p*(E(j)+w),
所以正向dp錯就錯在$dp[v]+=\frac{dp[u]+e(邊權)}{cnt[u]}$加的那個"dp[u]+e",
至於我之前說的正向dp也可以正確,泥萌應該也有點思路了吧:
因為構成來源狀態的期望值的那些概率的和g(j)不等於1,所以不能直接加上權值w,
那麽我們就在dp的同時維護出從起點到每個狀態的概率g,
然後再看上面的E(j→ i) = E(j)+p1*w+p2*w+p3*w+..+pn*w = E(j) + g(j)*w
也就是說dp本題正向dp的轉移寫成$dp[v]+=\frac{dp[u]+g[u]*e(邊權)}{cnt[u]}$就完成沒問題啦。
(實測AC,代碼在下面的註釋部分)
啰啰嗦嗦地說了1mol,希望對泥萌在理解正向和反向進行期望dp方面能有所幫助。
代碼:
#include<bits/stdc++.h> #define MAXN 100005 using namespace std; struct Edge{ int ent; int to[MAXN*2],val[MAXN*2],nxt[MAXN*2],head[MAXN]; Edge():ent(2){} void Adde(int u,int v,int w){ to[ent]=v; val[ent]=w; nxt[ent]=head[u]; head[u]=ent++; } }E,F; double dp[MAXN]; int order[MAXN],in[MAXN],cnt[MAXN]; int N,M,ont; void bfs(){ static queue<int>Q; Q.push(1); while(!Q.empty()){ int u=Q.front(); Q.pop(); order[++ont]=u; for(int e=E.head[u];e;e=E.nxt[e]){ int v=E.to[e]; in[v]--; if(!in[v]) Q.push(v); } } } int main(){ ios::sync_with_stdio(0); cin>>N>>M; for(int i=1,u,v,w;i<=M;i++) cin>>u>>v>>w,E.Adde(u,v,w),in[v]++,cnt[u]++,F.Adde(v,u,w); bfs(); for(int i=N-1;i>=1;i--){ int u=order[i]; for(int e=E.head[u];e;e=E.nxt[e]){ int v=E.to[e]; dp[u]+=(dp[v]+E.val[e])/cnt[u]; } } cout<<fixed<<setprecision(2)<<dp[1]<<endl; /*正推 static double g[MAXN]; g[1]=1; for(int i=2;i<=N;i++){ int u=order[i]; for(int e=F.head[u];e;e=F.nxt[e]){ int v=F.to[e]; dp[u]+=(dp[v]+g[v]*F.val[e])/cnt[v]; g[u]+=g[v]/cnt[v]; } } cout<<fixed<<setprecision(2)<<dp[N]<<endl; */ return 0; }
●Joyoi 綠豆蛙的歸宿