[Noi2012]迷失游乐园

Description

放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩。进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点、m条道路的无向连通图,且该图中至多有一个环(即m只可能等于n或者n-1)。小Z现在所在的大门也正好是一个景点。小Z不知道什么好玩,于是他决定,从当前位置出发,每次随机去一个和当前景点有道路相连的景点,并且同一个景点不去两次(包括起始景点)。贪玩的小Z会一直游玩,直到当前景点的相邻景点都已经访问过为止。小Z所有经过的景点按顺序构成一条非重复路径,他想知道这条路径的期望长度是多少?小Z把游乐园的抽象地图画下来带回了家,可是忘了标哪个点是大门,他只好假设每个景点都可能是大门(即每个景点作为起始点的概率是一样的)。同时,他每次在选择下一个景点时会等概率地随机选择一个还没去过的相邻景点。

Input

第一行是两个整数n和m,分别表示景点数和道路数。 接下来行,每行三个整数Xi, Yi, Wi,分别表示第i条路径的两个景点为Xi, Yi,路径长Wi。所有景点的编号从1至n,两个景点之间至多只有一条道路。

Output

 共一行,包含一个实数,即路径的期望长度,保留五位小数

Solution

比较麻烦的图论期望题。

花费3hWA一次,然后AC了此题。

和网上的题解大致差不多。肯定也要分树形和基环树两种情况讨论。

首先考虑树的:

本人没有用什么up数组。只有一个E[x],P[x]

表示,从x往下走的期望长度。到点X的概率。

对于一棵树,直接dfs1,找到以1为根的信息。

然后换根即可。dfs2就是换根、。

ans[x]={ len[fa]+(ans[fa]-(E[x]/P[x]+len[fa])*1/d[fa])*d[fa]/(d[fa]-1) }*1/d[x]   +  E[x]/P[x] * (d[x]-1)/d[x]

len[fa]表示,x到fa的边的长度。ans[i]表示,从i出发的期望长度。

貌似非常复杂。因为少记一个up嘛。

就是,算了两个部分,x向上走到fa的贡献,和向下走到儿子的贡献。

前半部分向上走的:

算ans[fa]抛去x的部分:E[x]/P[x],把x向下走的贡献变成P[x]=1的贡献,然后加上len[fa],再乘上fa到x的概率,就是1/d[fa]

减掉之后,由于其他部分,从ans[x]出发,每个点的概率是1/d[fa]的,但是,现在fa不是根,变成了1/(d[fa]-1)

所以,后面乘了一个d[fa]/(d[fa]-1),就变成了概率为1/(d[fa]-1)的情况。

再加上一个len[fa],就是如果走到fa的贡献了。当然还要有一个概率1/d[x]

后半部分向下走的:

E[x]/P[x],把从x出发的概率变成1,由于现在到每一个儿子的概率不是1/(d[x]-1)了,可以往上走,就变成了1/d[x]

也要调整一下。

就可以换根啦!

基环树:

  环比较麻烦。从下面的树往上走不好考虑。

  所以先处理环。

用一个栈dfs3找到所有的环上的点。是环上的点,记录进huan,共tot个。rac[i]=1,表示i是环上的点。

然后从这些环上点暴力dp,20n也不会TLE,找到ans[huan[i]]

环上暴力dp进入wrk函数。

钦定出发点st不能访问。发现,要么往左环上走,要么往右环上走,要么往下面的树走。

往树走直接dfs1即可。

往环走,发现,不经过st的情况下,也是遍历一个树诶!!!,只不过是“横躺”了环上的一部分

所以仍然可以dfs1处理。

然后,再对每一个环的每一个子树一遍dfs1,就得到了从这个子树的根(环点的一个儿子),往下走的E[x]了。

然后,对于每一个环上节点,分别往下换根。开始的时候,fa就是环上节点。

这是dfs4(其实和dfs2差不多)

换根的转移方程类似了。(不过,代码中是exp,传进来的时候其实就是ans[fa])

两个值得注意的细节:

1.dfs3找环的时候,第一次vis[y]就fl=true之后vis[y]都不可能再是环了。(可能不是正经的找基环的方法)

2.dfs1的时候,为了确定儿子的选择方法,可以暴力算一下,方便之后的转移概率处理。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=100000+5;
int n,m;
struct node{
    int nxt,to,val;
}e[2*N];
int hd[N],cnt;
double E[N],P[N];
double ans[N];
double op;
int d[N];
void add(int x,int y,int z){
    e[++cnt].nxt=hd[x];
    e[cnt].to=y;
    e[cnt].val=z;
    hd[x]=cnt;
}
bool vis[N];
int rt;
void dfs1(int x,double to,int fa,int dis){//算子树期望 ,to是到这个点的概率,dis其实没用 
    E[x]=0.00;
    P[x]=to;
    double lv=0.0;
    int can=0;
    for(int i=hd[x];i;i=e[i].nxt){//注意这里,先暴力算一下能走几个儿子,避免概率算错。 
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        can++;
    }
    if(can!=0) lv=1.00/((double)can);//走每个儿子的概率,不要除以0 
    
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        dfs1(y,to*lv,x,dis+e[i].val);
        E[x]+=P[y]*(double)e[i].val+E[y];//注意,P[y]不要再乘E[y]了,传进去的时候,to*lv相当于已经乘过了 
    }
}
void dfs2(int x,double exp,int fa,int val){
    if(x!=1){//麻烦的换根方程式,注意不要除以0 
        if(d[fa]!=1) exp=((double)val+(exp-(E[x]/P[x]+(double)val)*(1.00/(double)d[fa]))*((double)d[fa]/((double)d[fa]-1.00)))*(1.00/(double)d[x]);
        else exp=(double)val*(1.00/(double)d[x]);
        exp+=((double)E[x]/(double)P[x])*(((double)d[x]-1.00)/((double) d[x]));
        ans[x]=exp;
    }
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        dfs2(y,exp,x,e[i].val);
    }
}
int rac[N];
int huan[N],tot;
int sta[N],top;
bool fl=false;
void dfs3(int x,int fa){
    sta[++top]=x;
    vis[x]=1;
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]){
            if(fl) continue;
            fl=true;//注意这个fl,保证第一次找到vis的情况(也就是环),就不会再进行了,因为回溯的时候,会再vis[y]==1一次 
            int z;
            do{
                z=sta[top];
                rac[z]=1;
                huan[++tot]=z;
                top--;
            }while(z!=y);
        }
        else dfs3(y,x);
    }
    if(sta[top]==x) top--;
}
void dfs4(int x,double exp,int fa,int val){//类似的基环树换根 
    if(d[fa]!=1) exp=((double)val+(exp-(E[x]/P[x]+(double)val)*(1.00/(double)d[fa]))*((double)d[fa]/((double)d[fa]-1.00)))*(1.00/(double)d[x]);
    else exp=(double)val*(1.00/(double)d[x]);
    exp+=((double)E[x]/(double)P[x])*(((double)d[x]-1.00)/((double) d[x]));
    ans[x]=exp;
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        dfs4(y,ans[x],x,e[i].val);
    }
}
double wrk(int st){//计算基环树环点的ans 
    memset(E,0,sizeof E);
    vis[st]=1;
    double ret=0.0;
    rt=0;
    if(rac[st]){
        for(int i=hd[st];i;i=e[i].nxt){
            int y=e[i].to;
            if(rac[y]){
                dfs1(y,1.00/(double)d[st],st,e[i].val);
                ret+=P[y]*e[i].val+E[y];
            }
            else{
                dfs1(y,1.00/(double)d[st],st,e[i].val);
                ret+=P[y]*e[i].val+E[y];
            }
        }        
    }
    vis[st]=0;
    return ret;
}
int main()
{
    scanf("%d%d",&n,&m);int x,y,z;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,z);
        d[x]++,d[y]++;
    }
    if(m==n-1){//是树 
        rt=1;
        dfs1(1,1.00,0,0);
        ans[1]=E[1];
        dfs2(1,E[1],0,0);
        op=0;
        for(int i=1;i<=n;i++){
            op+=(1.00/(double)n)*ans[i];
        }
        printf("%.5lf",op);
        return 0;
    }
    else{//是基环树 
        dfs3(1,0);    
        memset(vis,0,sizeof vis);
        
        for(int i=1;i<=tot;i++){
            ans[huan[i]]=wrk(huan[i]);
        }
        memset(vis,0,sizeof vis);
        for(int j=1;j<=tot;j++){
            int x=huan[j];
            vis[x]=1;
            for(int i=hd[x];i;i=e[i].nxt){
                int y=e[i].to;
                if(rac[y]) continue;
                dfs1(y,1.00,x,0);
            }
        }
        
        for(int j=1;j<=tot;j++){
            int x=huan[j];
            vis[x]=1;
            for(int i=hd[x];i;i=e[i].nxt){
                int y=e[i].to;
                if(rac[y]) continue;
                dfs4(y,ans[x],x,e[i].val);
            }
        }
        
        for(int i=1;i<=n;i++){
            op+=(1.00/(double)n)*ans[i];
        }
        printf("%.5lf",op);
        return 0;
    }    
}
原文地址:https://www.cnblogs.com/Miracevin/p/9592243.html