淺談期望的線性性(可加性)【CodeForces280c】【bzoj3036】【bzoj3143】


這里寫圖片描述
[pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=63399955
向大(hei)佬(e)勢力學(di)習(tou)

之前一直都沒有接觸過期望,更別說期望dp了。
先從期望說起吧,dp什么的先不談。淺談一下期望的線性性(可加性),這是一個很重要的性質,主要用我做的這幾道例題來更感性的理解(真的是淺談。。。orz)

首先,期望是指一個事件有多種結果,每一種結果出現有一定的可能性。
對於隨機變量x,它的期望E(x)=sigma{基本結果i發生的概率*發生基本結果i時的x的數值,i是一個基本結果}

然后是期望的線性性(可加性):
(感性理解一下)
E(X+Y)=E(X)+E(Y)
即兩個(或多個)隨機變量的和的期望等於期望的和

下面就由題目來理解理解吧(題才是重點):

Codeforces280c

題目大意
給出一棵含n個白點的有根樹,每次隨機選擇一個還沒有被染黑的節點,將這個節點和這個節點子樹中的所有點染黑.
問期望操作多少次后所有點都被染黑.
N<=100000

整棵樹的期望操作次數太大,難以找到方法。這時我們需要突破口。
該如何將大問題轉化為小問題呢?我們發現,一棵樹是可以分成好幾顆子樹的,而子樹分解的最終狀態就是所有的點。那么,我們是不是可以計算出 每個點被染黑的期望操作次數,然后相加就是整棵樹的了?答案是當然可以。
這里需要注意的是,對於每個點的操作次數是指的在這個點上的操作。對於每一個點,如果其祖先被染黑了,它自己也會被順帶染黑,而這個對於該點來說是沒有進行操作的。所以得出對於點x:E(x)=1/dep[x]
dfs就可以了

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=100000+5;

int n;
int head[N],end[N*2],nxt[N*2],hh=0;
double ans=0;

void dfs(int u,int f,int dep){
    ans+=1.0/dep;
    for(int i=head[u];i;i=nxt[i]){
        int v=end[i];
        if(v==f) continue;
        dfs(v,u,dep+1);
    }
}
void adde(int a,int b){
    hh++;
    end[hh]=b;
    nxt[hh]=head[a];
    head[a]=hh;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        adde(a,b),adde(b,a);
    }
    dfs(1,1,1);
    printf("%.20lf",ans);
    return 0;
}

bzoj3036綠豆蛙的歸宿

Description
隨着新版百度空間的下線,Blog寵物綠豆蛙完成了它的使命,去尋找它新的歸宿。
給出一個有向無環的連通圖,起點為1終點為N,每條邊都有一個長度。綠豆蛙從起點出發,走向終點。
到達每一個頂點時,如果有K條離開該點的道路,綠豆蛙可以選擇任意一條道路離開該點,並且走向每條路的概率為 1/K 。
現在綠豆蛙想知道,從起點走到終點的所經過的路徑總長度期望是多少?
Input
第一行: 兩個整數 N M,代表圖中有N個點、M條邊
第二行到第 1+M 行: 每行3個整數 a b c,代表從a到b有一條長度為c的有向邊
Output
從起點到終點路徑總長度的期望值,四舍五入保留兩位小數。
Sample Input
4 4
1 2 1
1 3 2
2 3 3
3 4 4
Sample Output
7.00
HINT
對於100%的數據 N<=100000,M<=2*N

很顯然,由期望的線性性可得:經過路徑期望總長度=sigma{每條邊期望經過次數*邊權}
因為是有向無環圖,所以 每條邊的期望經過次數=該邊起點的期望經過次數*從該起點出發經過該路徑的概率。
於是問題轉成了求每個點的期望經過次數。很顯然,每個點的期望經過次數=sigma{入邊i的期望經過次數}
於是發現邊與點的期望值是相輔相成的關系,由於是有向無環圖,所以拓撲排序就可以了

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;

const int N=100000+5;

int n,m,in[N],out[N];
int head[N],end[N*2],len[N*2],nxt[N*2],hh=0;
queue<int> q;
double p[N],ans=0;

void adde(int a,int b,int c){
    hh++;
    end[hh]=b;
    len[hh]=c;
    nxt[hh]=head[a];
    head[a]=hh;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        adde(a,b,c);
        in[b]++,out[a]++;
    }
    q.push(1);
    p[1]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i;i=nxt[i]){
            int v=end[i];
            ans+=p[u]/out[u]*len[i];
            p[v]+=p[u]/out[u];
            in[v]--;
            if(in[v]==0){
                q.push(v);
            }
        }
    }
    printf("%.2lf",ans);
    return 0;
}

bzoj3143[Hnoi2013]游走

Description
一個無向連通圖,頂點從1編號到N,邊從1編號到M。
小Z在該圖上進行隨機游走,初始時小Z在1號頂點,每一步小Z以相等的概率隨機選 擇當前頂點的某條邊,沿着這條邊走到下一個頂點,獲得等於這條邊的編號的分數。當小Z 到達N號頂點時游走結束,總分為所有獲得的分數之和。
現在,請你對這M條邊進行編號,使得小Z獲得的總分的期望值最小。
Input
第一行是正整數N和M,分別表示該圖的頂點數 和邊數,接下來M行每行是整數u,v(1≤u,v≤N),表示頂點u與頂點v之間存在一條邊。 輸入保證30%的數據滿足N≤10,100%的數據滿足2≤N≤500且是一個無向簡單連通圖。
Output
僅包含一個實數,表示最小的期望值,保留3位小數。
Sample Input
3 3
2 3
1 2
1 3
Sample Output
3.333

其實這道題與綠豆蛙有相似之處,但是圖變為了無向連通圖。
首先明確一個簡單的貪心:我們希望期望經過次數最大的邊編號最小。然后就是如何求期望經過次數了。原理與上一題一樣,由點推邊。
對於一號點,f[1]=1+sigma(f[j]/degree(j) , j和1有邊)
對於其他點,f[i]=sigma(f[j]/degree(j),j和i有邊)
但由於是無向圖,一個點可以被經過數次,還有極小的的可能在一條邊上來回走動。於是我們發現這個圖是有后效性的,每個點的值會互相影響,所以不能遞推。wa,那可怎么辦啊?
大佬告訴我,像這種會互相影響的值,其實和方程組很像,高斯消元即可。woo,學到了。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=500+5;
const double eps=1e-10;

int n,m;
int deg[N],edge[N*N][2];
int head[N],end[N*N*2],nxt[N*N*2],hh=0;
double g[N][N],p[N],ep[N*N];

void adde(int a,int b){
    hh++;
    end[hh]=b;
    nxt[hh]=head[a];
    head[a]=hh;
}
void solve(){
    int cnt=0;
    for(int k=1;k<n;k++){
        int j=-1;
        for(int i=cnt+1;i<n;i++){
            if(fabs(g[i][k])>eps){
                j=i;
                break;
            }
        }
        if(j==-1) continue;
        cnt++;
        for(int i=1;i<=n;i++) swap(g[cnt][i],g[j][i]);
        for(int i=1;i<n;i++){
            if(i==cnt) continue;
            if(fabs(g[i][k])<eps) continue;
            double tmp=g[i][k]/g[cnt][k];
            for(j=1;j<=n;j++)
                g[i][j]-=g[cnt][j]*tmp;
        }
    }
    for(int i=1;i<n;i++){
        p[i]=g[i][n]/g[i][i];
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        adde(a,b),adde(b,a);
        deg[a]++,deg[b]++;
        edge[i][0]=a,edge[i][1]=b;
    }
    for(int i=head[1];i;i=nxt[i]){
        int v=end[i];
        if(v==n) continue;
        g[1][v]=1.0/deg[v];
    }
    g[1][1]=-1;
    g[1][n]=-1;
    for(int k=2;k<n;k++){
        g[k][k]=-1;
        for(int i=head[k];i;i=nxt[i]){
            int v=end[i];
            if(v==n) continue;
            g[k][v]=1.0/deg[v];
        }
    }
    solve();
    for(int i=1;i<=m;i++){
        int a=edge[i][0],b=edge[i][1];
        ep[i]=p[a]/deg[a]+p[b]/deg[b];
    }
    sort(ep+1,ep+m+1);
    double ans=0;
    for(int i=1;i<=m;i++){
        ans+=ep[i]*(m-i+1);
    }
    printf("%.3lf",ans);
    return 0;
}

總結:
1、分解成小的、可計算的期望問題,來解決大的、不可計算的期望問題
2、無后效性的可直接遞推。有后效性的可解方程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM