• bzoj 2878: [Noi2012]迷失游乐园


    Description

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

    Solution

    基环外向树是树的一种特殊情况,先考虑树的做法
    (f[x]) 表示从 (x) 节点往子树内走 走到某个叶子节点的期望路径长度
    显然: (f[x]=frac{1}{|son|} sum f[son]+dis(son,x))

    (g[x]) 表示 (x) 节点往上走到某个叶子节点的期望路径长度
    (g[x]=dis(x,fa)+frac{(g[x]+f[x]*son[x]-f[u]-dis(x,fa))}{son[x]-1+1})

    考虑有一个环的情况,我们把环缩成一个点就相当是一棵树了,外向树的个数就是环长的大小
    对于外向树 (f[x]) 可以像树的情况一样求出来

    现在考虑用 (f[x]) 推出 (g[x]):
    首先看环上的点,(f[x]) 就是往这个点所在的外向树走, (g[x]) 就是走到环上其他点的外向树上去
    我们就只用DP是走到哪一棵外向树上就可以了

    从环上一个点出发有顺时针和逆时针两种走法,做法是一样的
    我们每走过环上一个点 (x),概率就要乘 (frac{1}{|son[x]|+1}),设概率为 (p)
    贡献就是 (p*frac{(f[x]*|son[x]|)}{(|son[x]|+1)}+dis(last,x)*p),(last) 是环上 (x) 的上一个点

    另外环上的点父亲不止一个,有些时候还需要特判,我开了一个 (sf[x]) 记录父亲的数量

    提供一份代码长度没有那么恐怖的代码.....

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    int n,m,head[N],nxt[N<<1],to[N<<1],num=1,dis[N<<1],sf[N];
    inline void link(int x,int y,int z){
    	nxt[++num]=head[x];to[num]=y;head[x]=num;dis[num]=z;}
    double f[N],g[N];int son[N];
    bool vis[N],flag=0;int pre[N],q[N],cnt=0,d[25][25];
    inline void dfs(int x,int last){
    	for(int i=head[x];i;i=nxt[i]){
    		int u=to[i];
    		if(u==last || vis[u])continue;
    		dfs(u,x);son[x]++;
    		f[x]+=f[u]+dis[i];
    	}
    	if(son[x])f[x]/=son[x];
    }
    inline void dfs2(int x,int last){
    	for(int i=head[x];i;i=nxt[i]){
    		int u=to[i];
    		if(u==last || vis[u])continue;
    		if(son[x]-1+sf[x])
    			g[u]=(g[x]*sf[x]+f[x]*son[x]-f[u]-dis[i])/(son[x]-1+sf[x]);
    		g[u]+=dis[i];
    		dfs2(u,x);
    	}
    }
    inline void circle(int x,int y){
    	while(x!=y)q[++cnt]=x,x=to[pre[x]^1];
    	q[++cnt]=y;
    }
    inline void dfs3(int x,int last){
    	vis[x]=1;
    	for(int i=head[x];i;i=nxt[i]){
    		int u=to[i];if(u==last)continue;
    		if(!vis[u])pre[u]=i,dfs3(u,x);
    		else{flag=1;pre[u]=i;circle(x,u);return ;}
    		if(flag)return ;
    	}
    	vis[x]=0;
    }
    int main(){
    	freopen("pp.in","r",stdin);
    	freopen("pp.out","w",stdout);
    	int x,y,z;
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&x,&y,&z);
    		link(x,y,z);link(y,x,z);
    	}
    	dfs3(1,1);
    	sf[1]=0;for(int i=2;i<=n;i++)sf[i]=1;
    	if(m==n-1)dfs(1,1),dfs2(1,1);
    	else{
    		sf[1]=1;
    		for(int i=1;i<=n;i++)vis[i]=0;
    		for(int i=1;i<=cnt;i++)vis[q[i]]=1;
    		for(int i=1;i<=cnt;i++)dfs(q[i],q[i]),sf[q[i]]=2;
    		for(int i=1;i<=cnt;i++){
    			int j=i<cnt?i+1:1;
    			d[i][j]=d[j][i]=dis[pre[q[i]]];
    		}
    		for(int i=1;i<=cnt;i++){
    			int k=i,t;double p=1.0;x=q[i];
    			for(int j=1;j<cnt;j++){
    				t=k<cnt?k+1:1;y=q[t];
    				if(j<cnt-1)g[x]+=p*(f[y]*son[y])/(son[y]+1)+p*d[k][t];
    				else g[x]+=p*(f[y]+d[k][t]);
    				k=t;p/=(son[y]+1);
    			}
    			k=i;p=1.0;
    			for(int j=1;j<cnt;j++){
    				t=k>1?k-1:cnt;y=q[t];
    				if(j<cnt-1)g[x]+=p*(f[y]*son[y])/(son[y]+1)+p*d[k][t];
    				else g[x]+=p*(f[y]+d[k][t]);
    				k=t;p/=(son[y]+1);
    			}
    			g[x]/=2.0;dfs2(x,x);
    		}
    	}
    	double ans=0;
    	for(int i=1;i<=n;i++)ans+=(f[i]*son[i]+g[i]*sf[i])/(son[i]+sf[i]);
    	printf("%.5lf
    ",ans/n);
    	return 0;
    }
    
    
  • 相关阅读:
    EffectiveC#17--装箱和拆箱的最小化
    EffectiveC#16--垃圾最小化
    EffectiveC#15--使用using和try/finally来做资源清理
    NET基础课--对象的筛选和排序(NET之美)
    Objective-C浅拷贝和深拷贝
    IOS viewdidload 方法在 init 方法之前调用
    [iOS]为什么不要在init初始化方法里调用self.view
    为什么init方法里有self.view就会先跑viewdidload方法
    IOS开发中重写init方法使用需谨慎
    The file “XXX.app” couldn’t be opened because you don’t have permission to view it.
  • 原文地址:https://www.cnblogs.com/Yuzao/p/8510966.html
Copyright © 2020-2023  润新知