BZOJ2878 [Noi2012]迷失游乐园

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

 

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

正解:基环外向树上DP

解题报告:

  $m=n-1$、$m=n$分别是树和基环外向树,显然基环外向树就是在树的情况下多了一个环,比树再多考虑一些环上的移动即可...

  树的情况我们考虑对于以每个点为起点时的情况,我们先随便找出一个点作为根节点,则每个点在树上要么往上走,要么往下走,走的期望长度分别设为$up[x]$和$down[x]$。

  先考虑往下走的情况,我们显然需要先知道每个儿子节点的$down$。

  那么$down[x]$就是所有儿子节点的$down$$+$儿子走到根这条边的距离的平均值,一遍$dfs$即可解决。

  $up[x]$有一点复杂,假设往上走,走到了父亲节点,那么又有两种方案,要么走进另外的儿子节点,要么继续往上走。

  所以就是走到儿子节点的期望长度与往上走的期望长度取平均值。

  树的情况比较simple。

  考虑基环外向树的情况,因为环的大小不会超过$20$,对于环上的每个节点,我可以先算出由这个节点拓展出去的树的情况,这与之前的树是一样的做法。

  但是$up$值计算不同:环上的一个点,可以往两个方向走,而概率的计算我们需要仔细想一想。令

  $sonnum[x]$为$x$的儿子节点数目,那么往子树走的贡献就可以直接算出来,继续在环上走的概率就为$frac{1}{sonnum[x]}$。

  按照这样的计算方法就可以算出环上的$up$值。

  最后再$dfs$一遍,就可以算出每棵子树的$up$值。

  统计最终答案的方法就是考虑一个点是往上还是往下,这里不赘述。我的代码似乎写复杂了一点,不过思想还是很好理解的。

  细节很多,细心调试...(祝身体健康)

//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 100011;
const int MAXM = 200011; 
int n,m,ecnt,first[MAXN],to[MAXM],next[MAXM],w[MAXM];
int son_num[MAXN],w_fa[MAXN],havfa[MAXN];
double down[MAXN],up[MAXN],ans;

inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

namespace Tree{
	inline void dfs1(int x,int fa){
		for(int i=first[x];i;i=next[i]) {
			int v=to[i]; if(v==fa) continue; havfa[v]=1;
			son_num[x]++; w_fa[v]=w[i]; dfs1(v,x);
			down[x]+=down[v]+w[i];
		}
		if(son_num[x]>0) down[x]/=(double)son_num[x];
	}

	inline void dfs2(int x,int fa){
		if(fa!=0) {
			up[x]=down[fa]*son_num[fa]-down[x]-w_fa[x]+up[fa];//从当前节点走上父亲再走下所有儿子节点,再考虑从父亲往上走的情况
			up[x]/=(double)(son_num[fa]+havfa[fa]-1); up[x]+=w_fa[x];//必须要判断父亲有没有父亲!否则不能往上走!
		}
		for(int i=first[x];i;i=next[i]) {
			int v=to[i]; if(v==fa) continue;
			dfs2(v,x);
		}
	}

	inline void work(){
		dfs1(1,0);
		dfs2(1,0);
	}
}

namespace Circle_and_tree{
	bool iscircle[MAXN],vis[MAXN];
	int pre[MAXN],father[MAXN],dui[45],cir_cnt,W[45][45];
	inline void dfs_circle(int x,int fa){//找环
		vis[x]=1;
		for(int i=first[x];i;i=next[i]) {
			int v=to[i]; if(v==fa) continue;
			if(vis[v]) { 
				if(iscircle[v]) continue;
				int u=x;
				while(1) {
					iscircle[u]=true; dui[++cir_cnt]=u;
					pre[u]=father[u];
					if(cir_cnt>1) W[cir_cnt][cir_cnt-1]=W[cir_cnt-1][cir_cnt]=w_fa[dui[cir_cnt-1]];
					if(u==v) break;
					u=father[u];
				}
				W[cir_cnt][1]=W[1][cir_cnt]=w[i];
				continue;
			}
			father[v]=x; w_fa[v]=w[i];
			dfs_circle(v,x);
		}
	}

	inline void dfs1(int x,int fa){//得到环上每个点向树中走的down值
		for(int i=first[x];i;i=next[i]) {
			int v=to[i]; if(iscircle[v] || v==fa) continue;
			dfs1(v,x);
			son_num[x]++; father[v]=x; w_fa[v]=w[i]; havfa[v]=1;
			down[x]+=down[v]+w[i];
		}
		if(son_num[x]) down[x]/=(double)son_num[x];
	}

	inline void dfs2(int x,int fa){//得到环上每个点向树中走的up值
		if(fa!=0) {//环上的点不用再算up值了
			up[x]=down[fa]*son_num[fa]-down[x]-w_fa[x]+up[fa]*havfa[fa];
			//同时考虑父亲节点可能是根节点,可以往环上两边走
			//从当前节点走上父亲再走下所有儿子节点,再考虑从父亲往上走的情况
			up[x]/=(double)(son_num[fa]+havfa[fa]-1); up[x]+=w_fa[x];//必须要判断父亲有没有父亲!否则不能往上走!
		}
		for(int i=first[x];i;i=next[i]) {
			int v=to[i]; if(v==fa || iscircle[v]) continue;
			dfs2(v,x);
		}
	}

	inline void work(){
		dfs_circle(1,0); int u,now,last,nex; double nowp;
		memset(father,0,sizeof(father));
		memset(w_fa,0,sizeof(w_fa));
		for(int i=1;i<=cir_cnt;i++) dfs1(dui[i],0);
		for(int i=1;i<=cir_cnt;i++) havfa[dui[i]]=2; //两个方向		
		for(int i=1;i<=cir_cnt;i++) {
			nowp=1.0; u=i+1; last=i; if(u>cir_cnt) u=1;
			while(u!=i) {//向编号变大方向
				now=dui[u];//从i走到环上的当前点
				//不要漏掉了环上点之间的长度!!!
				if((u%cir_cnt+1)==i) up[dui[i]]+=(double)nowp*(down[now]+W[last][u]);//特判最后一条环边不能走!
				else up[dui[i]]+=(double)nowp*(son_num[now]*down[now]/(son_num[now]+1)+W[last][u]);
				nowp/=(double)(son_num[now]+1); last=u;
				u++; if(u>cir_cnt) u=1;
			}

			nowp=1.0; u=i-1; last=i; if(u==0) u=cir_cnt;
			while(u!=i) {//向编号变小方向
				now=dui[u];//从i走到环上的当前点
				nex=u; nex--; if(nex==0) nex=cir_cnt;
				if(nex==i) up[dui[i]]+=(double)nowp*(down[now]+W[last][u]);//特判最后一条环边不能走!
				else up[dui[i]]+=(double)nowp*(son_num[now]*down[now]/(son_num[now]+1)+W[last][u]);
				nowp/=(double)(son_num[now]+1); last=u;
				u=nex;
			}
			up[dui[i]]/=2.0;//取平均值
		}
		for(int i=1;i<=cir_cnt;i++) dfs2(dui[i],0);
	}
}

inline void work(){
	n=getint(); m=getint(); int x,y,z;
	for(int i=1;i<=m;i++) {
		x=getint(); y=getint(); z=getint();
		next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; w[ecnt]=z;
		next[++ecnt]=first[y]; first[y]=ecnt; to[ecnt]=x; w[ecnt]=z;
	}
	if(m==n-1) Tree::work();
	else Circle_and_tree::work();

	for(int i=1;i<=n;i++) 
		ans+=(double)(down[i]*son_num[i]+up[i]*havfa[i])/(son_num[i]+havfa[i]);
	ans/=(double)n;//平均值
	printf("%.5lf",ans);
}

int main()
{
    work();
    return 0;
}

  

原文地址:https://www.cnblogs.com/ljh2000-jump/p/6385074.html