2878: [Noi2012]迷失游乐园

44 篇文章 0 订阅

2878: [Noi2012]迷失游乐园

Time Limit: 10 Sec   Memory Limit: 512 MB
Submit: 954   Solved: 542
[ Submit][ Status][ Discuss]

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

[ Submit][ Status][ Discuss]



先考虑没有环的情况,也就是一棵树,不妨以1为树根

假如从1出发,那一定只能往子树走,因为是树,所以没有回头路

令f[i]:以i为根的子树里,从i出发,期望路程

g[i]:整棵树以i为根,从i出发,期望路程

f[i]很好统计,然后利用f[i]统计g[i],方法不难,换根而已


如果有环又怎么做?题中指明,环最多一个,大小不超过20,这个环很小

可以注意到,环删掉任意一条边,都剩下一棵树,那就往这里转化

现假设让环上一点作为树根,作图

从x出发,如果走向别的子树(三角形代替),那么环上决策无影响

如果走了红边,蓝边一定不能走,如果走了蓝边,红边一定不能走

枚举走红边或者蓝边,就能转换成树上的情况了,影响的f值显然只有与该环有关的那个子树

而除了那个子树,其他子树(三角形)的g显然就都可以统计了

那这样O(n*环的大小)就能解决本题

#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<bitset>
using namespace std;

const int maxn = 1E5 + 10;
typedef double DB;

struct E{
	int to,w; E(){}
	E(int to,int w): to(to),w(w){}
};

int n,m,cnt,tp,tot,vis[maxn],s[maxn],stk[maxn];
bool bo[maxn];
DB Ans,f[maxn],g[maxn];

vector <E> v[maxn];

void Calc_f(int x)
{
	int son = 0; f[x] = 0.000;
	for (int i = 0; i < v[x].size(); i++)
	{
		E e = v[x][i];
		if (vis[e.to] == cnt) continue;
		vis[e.to] = cnt; Calc_f(e.to); ++son;
		f[x] += (DB)(e.w) + f[e.to];
	}
	if (son) f[x] /= (DB)(son);
}

void Calc_g(int x,DB t)
{
	int son = v[x].size(); DB sum = 0.000;
	for (int i = 0; i < v[x].size(); i++)
	{
		E e = v[x][i];
		if (vis[e.to] == cnt) continue;
		sum += f[e.to] + (DB)(e.w);
	}
	g[x] = (sum + t) / (DB)(son);
	for (int i = 0; i < v[x].size(); i++)
	{
		E e = v[x][i];
		if (vis[e.to] == cnt) continue;
		vis[e.to] = cnt;
		DB res = sum - f[e.to] - (DB)(e.w);
		DB Nex = (son > 1)?(t + res) / (DB)(son - 1) + (DB)(e.w):(DB)(e.w);
		Calc_g(e.to,Nex);
	}
}

bool Dfs(int x,int fa)
{
	stk[++tp] = x;
	for (int i = 0; i < v[x].size(); i++)
	{
		E e = v[x][i];
		if (e.to == fa) continue;
		if (vis[e.to] == cnt)
		{
			for (;;)
			{
				s[++tot] = stk[tp]; bo[s[tot]] = 1;
				if (stk[tp--] == e.to) return 1;
			}
		}
		vis[e.to] = cnt; 
		if (Dfs(e.to,x)) return 1;
	}
	--tp; return 0;
}

void Solve(int x)
{
	int son = v[x].size(); DB sum = 0.000; vis[x] = ++cnt;
	for (int i = 0; i < v[x].size(); i++)
	{
		E e = v[x][i];
		if (bo[e.to]) vis[x] = ++cnt; vis[e.to] = cnt;
		Calc_f(e.to); sum += ((DB)(e.w) + f[e.to]);
	}
	vis[x] = ++cnt; g[x] = sum / (DB)(son);
	for (int i = 0; i < v[x].size(); i++)
	{
		E e = v[x][i];
		if (bo[e.to]) continue;
		vis[e.to] = cnt;
		DB res = sum - f[e.to] - (DB)(e.w);
		DB Nex = res / (DB)(son - 1) + (DB)(e.w);
		Calc_g(e.to,Nex);
	}
}

int getint()
{
	char ch = getchar(); int ret = 0;
	while (ch < '0' || '9' < ch) ch = getchar();
	while ('0' <= ch && ch <= '9')
		ret = ret*10 + ch - '0',ch = getchar();
	return ret;
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	n = getint(); m = getint();
	for (int i = 1; i <= m; i++)
	{
		int x = getint(),y,w;
		y = getint(); w = getint();
		v[x].push_back(E(y,w));
		v[y].push_back(E(x,w));
	}
	if (n == m)
	{
		vis[1] = ++cnt; Dfs(1,0);
		for (int i = 1; i <= tot; i++) 
			Solve(s[i]);
	}
	else
	{
		vis[1] = ++cnt; Calc_f(1); 
		vis[1] = ++cnt; Calc_g(1,0);
	}
	for (int i = 1; i <= n; i++) Ans += g[i];
	printf("%.5f",Ans / (DB)(n));
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值