1. 程式人生 > >1018 - 數學期望 - 綠豆蛙的歸宿(luogu 4316)

1018 - 數學期望 - 綠豆蛙的歸宿(luogu 4316)

傳送門

 

分析

數學期望入門題

數學期望:隨機變數取值與概率的乘積之和

線性性:就是可以各種相加求解,然後亂搞dp

正經的,也就是說對於不相關的兩個隨機變數φ和ξ,E(φ±ξ)=E(φ)±E(ξ);E(φξ)=E(φ)E(ξ);E(φ/ξ)=E(φ)/E(ξ)

 摘自洛谷___new2zy___ :

根據題目要求,我們很自然的可以想到:

設狀態f[x]f[x]表示點x到終點n的期望路徑總長

顯然,要求的答案為f[1],而且有f[n]=0f[n]=0

(終點到自己的期望距離肯定為0啊。。。)

發現這時就是期望dp的套路了。。。

正好要將期望dp,不妨我們先來說說期望dp的具體sao操作

期望dp,也加概率dp

一般來說,期望dp找到正確的狀態後,轉移是比較容易想到的。

但一般情況下,狀態一定是“可數”的

事實上,將問題直接作為dp的狀態是最好的。

如,問“n人做XX事的期望次數”,那麼不妨設計狀態為f[i]表示i個人做完事的期望

轉移一般是遞推,通常分兩種,一種是從上一個狀態轉移得(填表法),另一種是轉移向下一個狀態(刷表法)。

有時期望dp需以最終狀態為初始狀態轉移,即逆推

如f[i]表示期望還要走f[i]步到達終點。這種狀態的轉移是刷表法

形如f[i]=∑p[i→j]*f[j]+w[i→j]f[i]=∑p[i→j]∗f[j]+w[i→j],其中p表示轉移的概率

w表示轉移對答案的貢獻

一般來說,初始狀態確定時可用順推,終止狀態確定時可用逆推。

大概期望dp的套路就是這樣了吧。。。(我還是菜講得不太好)

現在我們回到本題

上面提到了,我們設狀態f[x]表示點x到終點n的期望路徑總長,那麼顯然有f[n]=0

那麼這正好符合了“終止狀態確定時可用逆推”的策略套路

具體來說:

對於一條有向邊,我們假設它由 x->yx−>y

那麼有f[x]=(\dfrac{1}{degree[x]})*∑f[y]+w[x->y]f[x]=(degree[x]1​)∗∑f[y]+w[x−>y]

其中degree[x]degree[x]表示x點的度(結合一下上面給出的式子你就懂了)

仔細觀察題目其實你會發現, (\dfrac{1}{degree[x]})(degree[x]1​)其實就是概率(p)

同時又有一個問題,那就是轉移時的過程怎麼實現

不妨這樣想:既然是個DAG,那麼我們可以“倒過來”想

具體來講,我們反向連邊,進行一遍拓撲排序,在拓撲排序的時候進行期望dp的轉移

這時候要注意上面的x和y要反過來(因為我們反向連邊了)

那麼我們轉移方程就設計完啦

(其實還是挺好理解的是不是)(逃

分析一下複雜度

dp轉移是與拓撲排序有關的,每次計算幾乎是O(1)O(1)的

那麼時間複雜度瓶頸就是拓撲排序,故時間複雜度為O(n+m)O(n+m)

 

程式碼

#include<bits/stdc++.h>
#define N 100009
using namespace std;
int n,m;
int nxt[N<<1],head[N],to[N<<1],w[N<<1],cnt=0;
void add(int x,int y,int z){
	nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
int num[N],ru[N];
double dis[N];
queue<int > Q;
int main(){
	scanf("%d%d",&n,&m);
	int i,j,k,u,v,z;
	for(i=1;i<=m;++i){
		scanf("%d%d%d",&u,&v,&z);
		add(v,u,z);ru[u]++;num[u]++;
	}
	dis[n]=0;Q.push(n);
	while(!Q.empty()){
		int u=Q.front();Q.pop();
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			dis[v]+=(dis[u]+w[e])/num[v];
			ru[v]--;
			if(!ru[v]) Q.push(v);
		}
	}
	printf("%.2lf",dis[1]);
	return 0;
}