【基环树DP】[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

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

Sample Input

4 3
1 2 3
2 3 1
3 4 4

Sample Output


6.00000

【样例解释】样例数据中共有6条不同的路径: 路径 长度 概率
1–>4 8 1/4
2–>1 3 1/8
2–>4 5 1/8
3–>1 4 1/8
3–>4 4 1/8
4–>1 8 1/4
因此期望长度 = 8/4 + 3/8 + 5/8 + 4/8 + 4/8 + 8/4 = 6.00
【评分方法】本题没有部分分,你程序的输出只有和标准答案的差距不超过0.01时,才能获得该测试点的满分,否则不得分。
【数据规模和约定】对于100%的数据,1 <= Wi <= 100。 测试点编号 n m 备注
1 n=10 m = n-1 保证图是链状
2 n=100 只有节点1的度数大于2
3 n=1000 /
4 n=100000 /
5 n=100000 /
6 n=10 m = n /
7 n=100 环中节点个数<=5
8 n=1000 环中节点个数<=10
9 n=100000 环中节点个数<=15
10 n=100000 环中节点个数<=20

HINT


Source


分析

如果是树的话,可以向上 DP 一次,再向下 DP 一次求出解。

up:f[u]=f[u.son]degree[u](u==root)down:f[u.son]=(f[u]f[u.son]×degree[u]1degree[u])÷degree[u]1degree[u]+f[u.son]

对于基环树,我们可以拆成一个森林和一个环,其中每棵树的就是环上的点。
我们先从下往上DP,然后再在环上暴力更新环上点的答案,再从上往下更新一遍就好了。

代码

#include<cstdio>
#include<algorithm>
#define MAXN 100000
using namespace std;
int n,m,d[MAXN+10],bgcir,cir[MAXN+10],cnt;
double f[MAXN+10],ans,ff[MAXN+10],g[MAXN+10];
bool vis[MAXN+10];
void Read(int &x){
    char c;
    while(c=getchar(),c!=EOF)
        if(c>='0'&&c<='9'){
            x=c-'0';
            while(c=getchar(),c>='0'&&c<='9')
                x=x*10+c-'0';
            ungetc(c,stdin);
            return;
        }
}
struct node{
    int v,wt;
    bool vis;
    node *next,*back;
}*adj[MAXN+10],edge[MAXN*2+10],*ecnt=edge,*pre[MAXN+10];
inline void addedge(int u,int v,int wt){
    node *p=++ecnt;
    p->v=v;
    p->wt=wt;
    p->next=adj[u];
    adj[u]=p;
    p->vis=0;
    p=p->back=++ecnt;
    p->v=u;
    p->wt=wt;
    p->next=adj[v];
    adj[v]=p;
    p->vis=0;
    p->back=ecnt-1;
}
void read(){
    Read(n),Read(m);
    int i,u,v,wt;
    for(i=1;i<=m;i++){
        Read(u),Read(v),Read(wt);
        d[u]++,d[v]++;
        addedge(u,v,wt);
    }
}
void dfs(int u,int fa){
    for(node *p=adj[u];p;p=p->next){
        if(p->v!=fa&&!p->vis){
            dfs(p->v,u);
            f[u]+=f[p->v]+p->wt;
        }
    }
    if(fa){
        if(d[u]>1)
            f[u]/=d[u]-1;
    }
    else{
        if(d[u])
            f[u]/=d[u];
    }
}
void dfs2(int u,int fa){
    for(node *p=adj[u];p;p=p->next){
        if(p->v!=fa&&!p->vis){
            if(d[u]>1)
                f[p->v]=((f[u]-(f[p->v]+p->wt)/d[u])/(d[u]-1)*d[u]+p->wt)/d[p->v]+f[p->v]/d[p->v]*(d[p->v]-1);
            else
                f[p->v]=1.0*p->wt/d[p->v]+f[p->v]/d[p->v]*(d[p->v]-1);
            dfs2(p->v,u);
        }
    }
}
void solve(){
    dfs(1,0);
    dfs2(1,0);
    for(int i=1;i<=n;i++)
        ans+=f[i];
    ans/=n;
}
void find_cir(int u){
    if(vis[u]){
        bgcir=u;
        return;
    }
    vis[u]=1;
    for(node *p=adj[u];p;p=p->next){
        if(!p->vis){
            p->back->vis=p->vis=1;
            pre[p->v]=p;
            find_cir(p->v);
        }
    }
}
void solve2(){
    find_cir(1);
    int i=bgcir,j;
    for(node *p=edge;p<=ecnt;p++)
        p->vis=0;
    do{
        cir[++cnt]=i;
        pre[i]->vis=pre[i]->back->vis=1;
        i=pre[i]->back->v;
    }while(i!=bgcir);
    for(i=1;i<=cnt;i++){
        cir[i+cnt]=cir[i];
        d[cir[i]]-=2;
        dfs(cir[i],0);
        d[cir[i]]+=2;
    }
    for(i=1;i<=cnt;i++){
        g[cir[i+cnt-1]]=f[cir[i+cnt-1]];
        for(j=i+cnt-2;j>i;j--)
            g[cir[j]]=(g[cir[j+1]]+pre[cir[j]]->wt)/(d[cir[j]]-1)+f[cir[j]]/(d[cir[j]]-1)*(d[cir[j]]-2);
        g[cir[i]]=(g[cir[i+1]]+pre[cir[i]]->wt)/d[cir[i]];
        g[cir[i+1]]=f[cir[i+1]];
        for(j=i+2;j<i+cnt;j++)
            g[cir[j]]=(g[cir[j-1]]+pre[cir[j-1]]->wt)/(d[cir[j]]-1)+f[cir[j]]/(d[cir[j]]-1)*(d[cir[j]]-2);
        g[cir[i]]+=(g[cir[i+cnt-1]]+pre[cir[i+cnt-1]]->wt)/d[cir[i]];
        ff[cir[i]]=g[cir[i]]+f[cir[i]]/d[cir[i]]*(d[cir[i]]-2);
    }
    for(i=1;i<=cnt;i++){
        f[cir[i]]=ff[cir[i]];
        dfs2(cir[i],0);
    }
    for(i=1;i<=n;i++){
        ans+=f[i];
    }
    ans/=n;
}
int main()
{
    read();
    if(m==n-1)
        solve();
    else
        solve2();
    printf("%.5lf\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值