绿豆蛙的归宿

绿豆蛙的归宿

给定点数为n的dag以及边权,询问起点到终点经过的边权的期望值,(N<=100000)

法一:

期望题,考虑倒推,设(f[x])表示从点x到终点的路径期望长度,设y是一个与x相连的出点,设(out[x])为x的出点的个数,设(s[x][y])与x,y相连的边权,不难得知

[f[x]=frac{f[y]+dis[x][y]}{out[x]} ]

注意到只有当x算完后,才能继续向后算,于是考虑建反边,用拓扑排序的方法转移方程。

另外注意到深度优先搜索的特点,一定是该点的出点遍历完再转移该点,所以此处也可以用dfs实现。

参考代码:

拓扑排序

#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define lb long double
using namespace std;
struct point{
    point*next;int to,len;
}*head[100001],*pt;
lb dp[100001];
int team[100001],in[100001],dag[100001];
il void link(int,int,int),read(int&),
    bfs(int);
int main(){
    int n,m,i,j,k;
    read(n),read(m);
    while(m--)read(i),read(j),read(k),
                  link(j,i,k),++in[i];
    for(i=1;i<=n;++i)dag[i]=in[i];
    bfs(n),printf("%.2Lf",dp[1]);
    return 0;
}
il void bfs(int s){
    int h(0),t(1);team[1]=s;
    while(h<t){++h;
        for(pt=head[team[h]];pt!=NULL;pt=pt->next){
            dp[pt->to]+=(dp[team[h]]+pt->len)/in[pt->to];
            --dag[pt->to];if(!dag[pt->to])team[++t]=pt->to;
        }
    }
}
il void read(int&x){
    x&=0;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
il void link(int x,int y,int len){
    pt=new point,pt->to=y,pt->len=len;
    pt->next=head[x],head[x]=pt;
}

dfs版

#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define db double
#define exact 0.0001
using namespace std;
struct point{
    point*next;int to,len;
}*head[100001],*pt;
int out[100001];db dp[100001];
il db search(int);
il void read(int&),link(int,int,int);
int main(){
    int n,m,i,j,k;
    read(n),read(m);
    while(m--)read(i),read(j),read(k),
                  link(i,j,k),++out[i];
    search(1),printf("%.2lf",dp[1]);
    return 0;
}
il db search(int x){
    if(dp[x]>exact)return dp[x];
    for(point *i(head[x]);i!=NULL;i=i->next)
        dp[x]+=(search(i->to)+i->len)/out[x];
    return dp[x];
}
il void link(int x,int y,int len){
    pt=new point,pt->to=y,pt->len=len;
    pt->next=head[x],head[x]=pt;
}
il void read(int &x){
    x&=0;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

法二:

既然倒推能够实现,那么考虑顺推,设(out[x])表示x的出度,(e[x])为从起点到该点的路径长度的期望,(p[x])表示从起点到该点的概率,所以不难有,设y与x相连且为其出点,(s[x][y])为x间y的边权,不难有

[e[x]=e[y]/out[y]+s[x][y]p[y]/out[y],p[x]=p[y]/out[y] ]

顺着拓扑排序,同时维护概率和期望,当然你也可以倒过来dfs,终点点的编号对应的期望即我们的答案。

参考代码:

#include <iostream>
#include <cstdio>
#include <queue>
#define il inline
#define ri register
using namespace std;
struct point{
    point*next;int to,len;
}*head[100001],*pt;
double p[100001],e[100001];
int out[100001],in[100001];
il void link(int,int,int),read(int&);
int main(){
    int n,m,i,j,k;
    scanf("%d%d",&n,&m);
    while(m--)
        read(i),read(j),read(k),
            ++out[i],++in[j],link(i,j,k);
    queue<int>t;t.push(1),p[1]=1;
    while(!t.empty()){
        i=t.front(),t.pop();
        for(pt=head[i];pt!=NULL;pt=pt->next){
            p[pt->to]+=p[i]/out[i];
            e[pt->to]+=(e[i]+pt->len*p[i])/out[i];
            --in[pt->to];if(!in[pt->to])t.push(pt->to);
        }
    }printf("%.2lf",e[n]);
    return 0;
}
il void read(int &x){
    x&=0;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
il void link(int x,int y,int len){
    pt=new point,pt->to=y,pt->len=len;
    pt->next=head[x],head[x]=pt;
}

法三:

只对于一条边考虑,不难得知它的概率很好维护,法二已经介绍了方法,而我们完全可以只维护概率,撇开期望做,同样dagdp,也就是拓扑排序,不妨让变量与法二相同,不难有

[ans+=frac{p[y]}{out[y]}s[x][y],p[x]+=frac{p[y]}{out[y]} ]

以此维护累加答案即可,实际上仔细理解一下你会发现,加上的式子即法二期望递推方程的第二项,我们只是撇开了期望,其实你也可以这样理解,也就是所谓的法二中的期望也就是把法三中的(frac{p[y]}{out[y]}),给存到每个点再累加到终点。

参考代码:

#include <iostream>
#include <cstdio>
#include <queue>
#define il inline
#define ri register
using namespace std;
struct point{
    point*next;int to,len;
}*head[100001],*pt;
double p[100001],ans;
int out[100001],in[100001];
il void link(int,int,int),read(int&);
int main(){
    int n,m,i,j,k;
    scanf("%d%d",&n,&m);
    while(m--)
        read(i),read(j),read(k),
            ++out[i],++in[j],link(i,j,k);
    queue<int>t;t.push(1),p[1]=1;
    while(!t.empty()){
        i=t.front(),t.pop();
        for(pt=head[i];pt!=NULL;pt=pt->next){
            p[pt->to]+=p[i]/out[i];
            ans+=pt->len*p[i]/out[i];
            --in[pt->to];if(!in[pt->to])t.push(pt->to);
        }
    }printf("%.2lf",ans);
    return 0;
}
il void read(int &x){
    x&=0;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
il void link(int x,int y,int len){
    pt=new point,pt->to=y,pt->len=len;
    pt->next=head[x],head[x]=pt;
}

看完这三种思路,必然感慨良多,期望确实没有什么固定的套路,任何创新的拆分与不同的角度,都会带来不同的解决方案。

原文地址:https://www.cnblogs.com/a1b3c7d9/p/10812315.html