zoukankan      html  css  js  c++  java
  • 绿豆蛙的归宿

    绿豆蛙的归宿

    给定点数为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;
    }
    
    

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

  • 相关阅读:
    [十二省联考2019]字符串问题:后缀数组+主席树优化建图
    HAOI2018简要题解
    使用单调队列维护决策三元组实现决策单调性优化DP的一些细节
    杜教筛&min_25筛复习
    分治NTT:我 卷 我 自 己
    高级(并不)多项式算法总结
    导数与微分简单总结(updated)
    退役前的做题记录
    USACO2018DEC PLATINUM
    USACO2018DEC GOLD
  • 原文地址:https://www.cnblogs.com/a1b3c7d9/p/10812315.html
Copyright © 2011-2022 走看看