洛谷4316 bzoj3036 绿豆蛙的归宿 拓扑排序+期望dp

题目链接
题解:
本题介绍两种做法,一种是网上常见的倒着做的做法,另一种是正着做的做法。我看网上很多人说正着做是错的,而且显然是错的,我就很纳闷。因为我整理这题的时候是做题的几天之后,所以再次看到题后的第一感觉竟然是正着做,那么我就重新写了一份正着做的代码,在洛谷交了一下,发现也是能过的。那么我就想在此特地说明,这题不仅有网上各种反着从n到1的做法,还有可以直接从1到n的做法。
首先介绍网上的方法:
我们要求的答案应该是 每条边的期望经过次数 边权。
边权是已知的,那么我们只需要求出每条边的期望经过次数。
而每条边的期望经过次数 = = 出 发 点 的 期 望 经 过 次 数 出 发 点 的 出 度 数
每个点的出度是可以在读入边的时候处理好的,那么问题就是如何求每个点期望经过的次数。
我们发现,其实每个点期望的经过次数是所有到该点的边的期望次数之和,边的期望又可以由这条边的起始点的期望除以起始点的出度得到。那么我们就可以根据之前求过的点的期望和当前边的权值来继续下去。那么这个过程我们可以拓扑排序+dp来实现。
很多人说正着做不行是因为他们列了这样一个式子

dp[v]=iu>vdp[u]+dis[i]u d p [ v ] = ∑ i ∈ 边 集 u − > v d p [ u ] + d i s [ i ] u 的 出 度

他们说这个式子正着做是错的,所以不能正着做。暂时还没想好为什么正着是错的,但是写了一些发现确实是错的,而反着就是对的。

所以就是从n号点向1号点做拓扑排序+dp

//反过来建边,然后正向反向分别记录
//既要知道每个点可以由哪些点来,又要知道它能到哪些点 
#include <bits/stdc++.h>
using namespace std;

int n,m,hed1[200010],hed2[200010],cnt1,cnt2;
int ru[100010],ru1[100010];//记录每个点的入度和出度 
int q[500010],h,t;
struct node
{
    int next,to;
    double dis;
}a[400010],b[400010];
//a记每个点可以由哪些点来,b记它能到哪些点 
double dp[100010];
void add1(int from,int to,double dis)
{
    a[++cnt1].to=to;
    a[cnt1].dis=dis;
    a[cnt1].next=hed1[from];
    hed1[from]=cnt1;
}
void add2(int from,int to,double dis)
{
    b[++cnt2].to=to;
    b[cnt2].dis=dis;
    b[cnt2].next=hed2[from];
    hed2[from]=cnt2;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int x,y,dis;
        scanf("%d%d%d",&x,&y,&dis);
        //反着建,从n到1去dp 
        add1(y,x,dis);
        add2(x,y,dis);
        ++ru[x];
    }
    for(int i=1;i<=n;++i)
    ru1[i]=ru[i];
    q[1]=n;
    h=1;
    t=2;
    while(h!=t)
    {
        int x=q[h];
        for(int i=hed1[x];i;i=a[i].next)
        {
            int y=a[i].to;
            --ru[y];
            if(ru[y]==0)
            {
                q[t++]=y;
                for(int j=hed2[y];j;j=b[j].next)
                dp[y]+=(dp[b[j].to]+b[j].dis)/ru1[y];
            }
        }
        ++h;
    }
    printf("%.2lf\n",dp[1]);
    return 0;
}

下面介绍正着做:
反正不管直接正着算期望为什么是错的,我们正着算每个点经过的概率是对的。那么我们可以算出每个点经过的概率,然后做完之后再乘以边权,得到每条边的对总期望的贡献。
这个做法在洛谷数据是对的,其他OJ我没试。

//不明白为什么网上很多人说正着做是错的 
#include <bits/stdc++.h>
using namespace std;

int n,m,hed[100010],cnt,q[100010],h,t,ru[100010],chu[100010];
double dp[100010],ans;
struct node
{
    int next,to,from;
    double dis;
}a[200010];
void add(int from,int to,int dis)
{
    a[++cnt].to=to;
    a[cnt].dis=dis;
    a[cnt].from=from;
    a[cnt].next=hed[from];
    hed[from]=cnt;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        ++ru[y];
        ++chu[x];
    }
    dp[1]=1;
    h=1;
    t=2;
    q[1]=1;
    while(h!=t)
    {
        int x=q[h];
        for(int i=hed[x];i;i=a[i].next)
        {
            int y=a[i].to;
            --ru[y];
            dp[y]+=dp[x]/chu[x];
            if(!ru[y])
            q[t++]=y;
        }
        ++h;
    }
    for(int i=1;i<=m;++i)
    ans+=a[i].dis*dp[a[i].from]/chu[a[i].from];
    printf("%.2lf\n",ans);
    return 0;
}
    原文作者:拓扑排序
    原文地址: https://blog.csdn.net/forever_shi/article/details/80683388
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞