• [luogu2081 NOI2012] 迷失游乐园 (树形期望dp 基环树)


    传送门

    题目描述

    放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩。

    进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点、m条道路的无向连通图,且该图中至多有一个环(即m只可能等于n或者n-1)。小Z现在所在的大门也正好是一个景点。小Z不知道什么好玩,于是他决定,从当前位置出发,每次随机去一个和当前景点有道路相连的景点,并且同一个景点不去两次(包括起始景点)。贪玩的小Z会一直游玩,直到当前景点的相邻景点都已经访问过为止。

    小Z所有经过的景点按顺序构成一条非重复路径,他想知道这条路径的期望长度是多少?

    小Z把游乐园的抽象地图画下来带回了家,可是忘了标哪个点是大门,他只好假设每个景点都可能是大门(即每个景点作为起始点的概率是一样的)。同时,他每次在选择下一个景点时会等概率地随机选择一个还没去过的相邻景点。

    【评分方法】本题没有部分分,你程序的输出只有和标准答案的差距不超过0.01时,才能获得该测试点的满分,否则不得分。

    输入输出格式

    输入格式:

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

    输出格式:

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

    输入输出样例

    输入样例#1:

    4 3
    1 2 3
    2 3 1
    3 4 4

    输出样例#1:

    6.00000000

    说明

    【样例解释】样例数据中共有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

    题解

    这个题细节挺多的(调了好长时间)
    先考虑树形状态下,由于是树那么只会是 从上往下走 或 从下往上 或 从下往上再往下qwq
    那么先考虑从上往下 down[u]表示u从上往下的期望路程 son[u]表示u的子节点数
    那么有(down[u]=frac{sum down[v]+dis(u,v)}{son[u]})
    其中v是u的子节点 显然可以一遍dfs解决
    在考虑从下往上 up[u]表示u从下往上的期望路程 pre为u的父节点
    nfa[pre]=1表示pre有父节点 =0则没有
    (up[u]=frac{down[pre]*son[pre]-down[u]-dis[pre,u]+up[pre]*nfa[pre]}{son[pre]-1+nfa[pre]})
    这表示父节点除了下到u之外情况的期望值除以所有可能情况
    在式子中表示为pre原来的全部期望值减去下到u的期望值加上(如果有父节点)到pre父节点的期望值
    这实际上等价于u这个点上到pre的期望值除以所有情况(到父亲的父亲去或其他子节点) 同样一遍dfs解决
    最后将每个节点的期望路程统计出来求出平均值即可

    那么我们在树的基础上在考虑一下有一个环的树
    我们发现题目中说环上点的个数最多只有20个,那么显然我们可以暴力求解。。
    首先找出环,然后以环上每个点为起点跑一次树求down的过程
    up的值显然不能直接求,因为有可能从环上跑到别的树上去
    所以我们需要在环上进行一些处理
    仍然是枚举环上所有的点,我们现在要求上到环上一点u之后的期望路程
    我们先要清楚环上一点(x)到下一点(y)的概率
    (p[y]=p[x]*frac{1}{son[x]+1})
    所以不到下一个点而是下到x的子树中的概率
    (P=frac{son[x]}{son[x]+1})
    然后我们记录u到x的距离为d,则u到x(并且进x的子树中)的期望值为
    (up[u]+=frac{(down[x]+d)*son[x]}{son[x]+1})
    枚举u和x我们就得到了环上每个点的up值
    然后再以每一个环上的点为根用树上的up值更新方法最终就能求出所有点的up值
    最后的最后跟树一样统计每个节点的期望值求平均即可
    好像是有点麻烦
    code:

    //By Menteur_Hxy
    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #define E(i,u) for(register int i=head[u];i;i=nxt[i])
    #define F(i,a,b) for(register int i=(a);i<=(b);i++)
    #define add(a,b,c) nxt[++cnt]=head[a],to[cnt]=b,w[cnt]=c,head[a]=cnt
    #define insert(a,b,c) add(a,b,c),add(b,a,c) 
    using namespace std;
    
    int rd() {
    	int x=0,f=1; char c=getchar();
    	while(!isdigit(c)) {if(c=='-') f=-f; c=getchar();}
    	while(isdigit(c)) x=(x<<1)+(x<<3)+c-48,c=getchar();
    	return x*f;
    }
    
    const int N=100010;
    int n,m,cnt;
    int nxt[N<<1],to[N<<1],w[N<<1],head[N];
    int snum[N],dis[N],vis[N],nfa[N];
    double up[N],down[N],ans;
    
    void dfs_down(int u) {
    	vis[u]=1; int v;
    	E(i,u) if(!vis[to[i]]) 
    		v=to[i],dis[v]=w[i],dfs_down(v),snum[u]++,down[u]+=down[v]+w[i];
    	if(snum[u]) down[u]/=double(snum[u]); vis[u]=0;
    }
    
    void dfs_up(int u,int pre) {
    	vis[u]=1; if(pre) nfa[u]=1;
    	if((snum[pre]-1+nfa[pre])&&pre) 
    		up[u]+=(down[pre]*snum[pre]-down[u]-dis[u]+up[pre]*nfa[pre])/double(snum[pre]-1+nfa[pre]);
    	E(i,u) if(!vis[to[i]]) up[to[i]]+=w[i],dfs_up(to[i],u);
    }
    
    int tot,len;
    int dfn[N],cirn[N],cire[N],fa[N];
    bool flag,inc[N];
    void tarjan(int u,int pre) {
    	if(flag) return ;
    	dfn[u]=++tot; fa[u]=pre;
    	E(i,u) { int v=to[i];
    		if(v==pre or flag) continue;
    		if(!dfn[v]) dis[v]=w[i],tarjan(v,u);
    		else if(dfn[v]>dfn[u]) {
    			cire[1]=w[i];
    			for(int now=v;now!=fa[u];now=fa[now]) 
    				cirn[++len]=now,cire[len+1]=dis[now],nfa[now]=2,vis[now]=1;
    			flag=1;
    		}
    	}
    }
    
    void cir_calc(int u,int k,int reg) {
    	double t=0.5,d=0;
    	F(i,1,len-1) {
    		if(reg==-1) d+=cire[k]; k+=reg;
    		if(!k) k=len; if(k==len+1) k=1;
    		if(reg==1) d+=cire[k];
    		if(i==len-1) up[u]+=t*(down[cirn[k]]+d);
    		else up[u]+=t*(down[cirn[k]]+d)*snum[cirn[k]]/double(snum[cirn[k]]+1);
    		t/=double(snum[cirn[k]]+1);
    	}
    }
    
    void cireework() {
    	tarjan(1,0);
    	F(i,1,len) dfs_down(cirn[i]),vis[cirn[i]]=1;
    	F(i,1,len) cir_calc(cirn[i],i,1),cir_calc(cirn[i],i,-1);
    	F(i,1,len) dfs_up(cirn[i],0);
    }
    
    int main() {
    	n=rd(),m=rd();
    	F(i,1,m) {
    		int a=rd(),b=rd(),c=rd();
    		insert(a,b,c);
    	}
    	if(m==n-1) dfs_down(1),dfs_up(1,0);
    	else cireework();
    	F(i,1,n) ans+=(double)(down[i]*snum[i]+up[i]*nfa[i])/double(snum[i]+nfa[i]);
    	ans/=double(n);
    	printf("%.5lf",ans);
    	return 0;
    }
    
    版权声明:本文为博主原创文章,未经博主允许不得转载。 博主:https://www.cnblogs.com/Menteur-Hxy/
  • 相关阅读:
    静态库与动态库的创建与使用
    MinGW 仿 linux 开发环境
    SICP 1.7-1.8 solution (Scheme)
    PHP 学生管理系统实现
    【2014最新】常用hosts集锦,分享给大家
    【Android快速入门3】布局简介及例子
    【Android快速入门2】拨号器的实现
    【Android快速入门1】目录结构及adb命令(以API19为例)
    基于深度及广度优先搜索的迷宫问题的演示
    基于HTML5的js构造爱心,动态时间校准
  • 原文地址:https://www.cnblogs.com/Menteur-Hxy/p/9281103.html
Copyright © 2020-2023  润新知