【题解】洛谷2081 [NOI2012]迷失游乐园

本文探讨了在基环树上求任意一点往特定方向走到头的路径期望长度问题。首先介绍了树上DP方法,定义了up[i]和down[i]数组来存储节点i向上和向下走到头的期望距离。随后,详细讲解了如何处理基环树的特殊性,包括环上点的up值计算和环上点儿子的up值更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题外话

这道题很久之前听教练讲过,当时觉得太麻烦就没写,后来拾起来的时候就发现自己不会了 QwQ (可能是因为我太菜了吧) ,于是怒肝了两个下午把它弄了出来,中间还出了一次大锅,所以留个博客以作纪念吧


题面

题面异常简单,就是求一颗基环树上的任意一点往某个方向走到头的路径期望长度。这个一看就是要用某种类似treeDPtreeDPtreeDP 的方法处理的(但是我一开始没看出来),所以我们可以先从简单问题入手


50分——树上dp

我们先考虑没有环的情况,那显然要用树上DP来解决——我们定义两个数组 up[i]up[i]up[i]down[i]down[i]down[i] 分别代表从 iii 节点向上和向下走到头的期望距离

那么 down[i]down[i]down[i] 应该是相当好转移的,因为它不牵扯拐弯。做一遍 dfsdfsdfs 然后统计每一个儿子的 downdowndown 即可求出 down[i]down[i]down[i] 了,转移方程用手推一下应该就能出来:
down[i]=∑i−>j(down[j]+len(i,j))son[i] down[i]=\frac{\sum_{i->j}(down[j]+len(i,j))}{son[i]} down[i]=son[i]i>j(down[j]+len(i,j))
这里 jjjiii 的每一个儿子,len(i,j)len(i,j)len(i,j) 表示 iiijjj 这条边的长度,son[i]son[i]son[i] 表示 iii 的儿子数量

但是 up[i]up[i]up[i] 就有点难搞,因为这条路有可能往上走了几步又拐到向下,而且你还得处理每一个位置拐弯的情况。这就非常麻烦了

我们考虑,如果我们处理出了每一个点的 downdowndown,然后从上往下 dpdpdp,那么我们这条路的期望会是什么样子的呢?

我们从这幅图上可以很清楚的看到:这个期望一共涉及两种方向,也就是上面的 222 和 $3 $。

这个 222 就相当于是从iii 的父亲 f 出发向下不经过 iii 的所有路径的期望,可以表示为 down[f]×son[f]−down[i]−len(f,i)down[f]\times son[f]-down[i]-len(f,i)down[f]×son[f]down[i]len(f,i)

那如果继续向上,就相当于是从 fff 向上随便走,其实就是 up[f]up[f]up[f]了。

所以我们就可以总结出一个计算 up[i]up[i]up[i] 的公式了:
up[i]=len(f,i)+down[f]×son[f]−down[i]−len(f,i)+up[f]deg[f]−1 up[i]=len(f,i)+\frac{down[f]\times son[f]-down[i]-len(f,i)+up[f]}{deg[f]-1} up[i]=len(f,i)+deg[f]1down[f]×son[f]down[i]len(f,i)+up[f]
这里 deg[f]deg[f]deg[f] 表示 fff 的度数,也就是这个点连出的边数。容易看出, deg[f]−1=(son[f]+1)−1=son[f]deg[f]-1=(son[f] +1)-1=son[f]deg[f]1=(son[f]+1)1=son[f]。所以直接把分母改写成 son[f]son[f]son[f] 即可。

最后怎么统计答案呢?参考一下上面那幅图就可以推出:
ans=∑up[i]+down[i]∗son[i]son[i]+1 ans=\sum \frac{up[i]+down[i]*son[i]}{son[i]+1} ans=son[i]+1up[i]+down[i]son[i]
那么树上的问题就解决了

(对了我这里少说了一句,其实这样写在洛谷上是可以混过两个基环点的哈哈哈哈)


100分——基环树特殊处理

既然环上的点不多,只有20个,我们就可以轻松 dfsdfsdfs 找出这些点并把它们用一个类似链表的方式保存起来(就是保存每一个点的下一位和上一位)

然后我们会发现,这个环貌似对环外的 upupupdowndowndown 没有任何影响,所以我们就可以对于每一棵小树 dfsdfsdfs 求出 downdowndown

那么根据刚才的经验,我们就可以通过这个 downdowndown 来推算每个点的 upupup

我们先来说环上的 upupup 值怎么求:

我们举个例子,如果环上的点是 1,2,3,4,51,2,3,4,51,2,3,4,5,我们钦定方向为顺时针,那么对于 111 来说走到 222 的可能性就是 111。从 222 走到 333 的可能性就是 1son[2]+1\frac{1}{son[2]+1}son[2]+11,从 333 走到 444 的可能性就是 1son[3]+1\frac{1}{son[3]+1}son[3]+11 。。。以此类推

那么我们就可以顺时针逆时针都跑一遍,然后统计答案时除以 2 即可。

表达式不太好看,所以这里就给出一个顺时针的:
up[i]=∑j!=ij=nex[i](∏k!=jk=nex[i]1son[k]+1×len(last[j],j)+down[j]∗son[j]son[j]+1) up[i]=\sum_{j!=i}^{j=nex[i]} (\prod_{k!=j}^{k=nex[i]} \frac{1}{son[k]+1}\times\frac{len(last[j],j)+down[j]*son[j]}{son[j]+1}) up[i]=j!=ij=nex[i](k!=jk=nex[i]son[k]+11×son[j]+1len(last[j],j)+down[j]son[j])
逆时针同理即可。

如果觉得没看懂表达式的话,这里上一段代码和一张图帮助各位理解:

double now=1;
for(int j=nx[x];j!=x;j=nx[j])
{
    if(/**/) //这里其实还有一个特判,请自行推算
        up[x]+=now*(h[j]+down[j]*son[j]/(son[j]+1));
	else /**/;
	now=now/(son[j]+1);
}

这里 nownownow 就是上面的 ∏1son[k]+1\prod \frac{1}{son[k]+1}son[k]+11,而 h[i]h[i]h[i] 就是一个预处理的 len(last[j],j)len(last[j],j)len(last[j],j)。上面所说的特判其实就是从环首走了一圈又回来的状况,请各位自行推算,这里就不再赘述了

还有一个小问题就是如何处理环上各点的儿子的 upupup 。因为这些点的父节点左右有两条连边,所以这些店需要单独拿出来计算 up[i]up[i]up[i]
up[i]=len(f,i)+down[f]×son[f]−down[i]−len(f,i)+up[f]×2deg[f]−1 up[i]=len(f,i)+\frac{down[f]\times son[f]-down[i]-len(f,i)+up[f]\times2}{deg[f]-1} up[i]=len(f,i)+deg[f]1down[f]×son[f]down[i]len(f,i)+up[f]×2
注意一下这里的 deg[f]−1≠son[f]deg[f]-1\ne son[f]deg[f]1̸=son[f]

统计答案的时候与之前的式子相似,树上的按照树上的统计,环上的就是上面加一个 up[i]up[i]up[i] ,下面加 111 就行了,也就是下面的这个式子:
ans=∑up[i]×2+down[i]∗son[i]son[i]+2 ans=\sum \frac{up[i]\times 2+down[i]*son[i]}{son[i]+2} ans=son[i]+2up[i]×2+down[i]son[i]


还有就是这几个期望计算的时候分母都不能是 000 ,所以需要手动加几个特判才行。

好了多的不说了,具体细节请看代码吧:


code

#include<vector>
#include<string.h>
#include<iostream>
#define N 100005
using namespace std;
int n,m,flag,nexflag,vis[N],son[N],fa[N],nx[N],h[N],nh[N],tag[N];
double ans,down[N],up[N];
struct nod{
	int to,val;
};
vector<nod> edge[N];
vector<int> lay[N],loop;
void addedge(int u,int v,int w)
{
	edge[u].push_back((nod){v,w});
	edge[v].push_back((nod){u,w});
}
void dfs1(int x)
{
	down[x]=0;
	for(int i=0;i<edge[x].size();i++)
	{
		nod nex=edge[x][i];
		if(!vis[nex.to])
		{
			vis[nex.to]=1;
			fa[nex.to]=x;
			h[nex.to]=nex.val;
			dfs1(nex.to);
			down[x]+=down[nex.to]+nex.val;
			son[x]++;
		}
	}
	if(son[x]>0)
		down[x]/=son[x];
}
void dfs2(int x)
{
	int f=fa[x];
	if(son[f]>1)
	{
		if(!tag[x])
			up[x]=(double)(down[f]*son[f]-down[x]-h[x]+up[f])/(edge[f].size()-1)+h[x];
		else	up[x]=(double)(down[f]*son[f]-down[x]-h[x]+up[f]*2)/(edge[f].size()-1)+h[x];
	}
	else	up[x]=up[f]+h[x];
	for(int i=0;i<edge[x].size();i++)
	{
		nod nex=edge[x][i];
		if(!vis[nex.to])
		{
			vis[nex.to]=1;
			dfs2(nex.to);
		}
	}
}
void dfs3(int x,int pre)
{
	vis[x]=1;
	if(flag!=0)
		return;
	for(int i=0;i<edge[x].size();i++)
	{
		nod nex=edge[x][i];
		if(nex.to!=pre)
		{
			if(vis[nex.to])
			{
				if(flag!=0)	return;
				flag=x;

				nexflag=nex.to;
				fa[nex.to]=x;
				nx[x]=nex.to;
				h[nex.to]=nh[x]=nex.val;
				return;
			}
			if(flag!=0)	return;
			fa[nex.to]=x;
			nx[x]=nex.to;
			h[nex.to]=nh[x]=nex.val;
			dfs3(nex.to,x);
			if(flag!=0)	return;
		}
	}
}
void cl_vis()
{
	memset(vis,0,sizeof(vis));
	for(int i=0;i<loop.size();i++)
		vis[loop[i]]=1;
}
int main()
{
	int u,v,w;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>u>>v>>w;
		addedge(u,v,w);
	}
	if(m==n-1)
	{
		vis[1]=1;
		dfs1(1);
		memset(vis,0,sizeof(vis));
		vis[1]=1;
		dfs2(1);
		for(int i=1;i<=n;i++)
			ans+=(double)(down[i]*son[i]+up[i])/(son[i]+(fa[i]!=0?1:0));
		printf("%.5f",ans/n);
	}
	else
	{
		dfs3(1,0);
		for(int i=flag;i!=nexflag;i=fa[i])
			loop.push_back(i),vis[i]=1;
		loop.push_back(nexflag);vis[nexflag]=1;
		cl_vis();
		int sz=loop.size();
		for(int i=0;i<sz;i++)
			dfs1(loop[i]);
		for(int i=sz-1;i>=0;i--)
		{
			int x=loop[i];
			double now=1;
			up[x]=0;
			for(int j=nx[x];j!=x;j=nx[j])
			{
				if(nx[j]!=x)
					up[x]+=now*(h[j]+down[j]*son[j]/(son[j]+1));
				else	up[x]+=now*(h[j]+down[j]);
				now=now/(son[j]+1);
			}
			now=1;
			for(int j=fa[x];j!=x;j=fa[j])
			{
				if(fa[j]!=x)
					up[x]+=now*(nh[j]+down[j]*son[j]/(son[j]+1));
				else	up[x]+=now*(nh[j]+down[j]);
				now=now/(son[j]+1);
			}
			up[x]/=2;
		}
		cl_vis();
		for(int i=0;i<sz;i++)
			for(int j=0;j<edge[loop[i]].size();j++)
			{
				nod nex=edge[loop[i]][j];
				if(!vis[nex.to])
					vis[nex.to]=tag[nex.to]=1,dfs2(nex.to);
			}	
		cl_vis();
		for(int i=1;i<=n;i++)
		{
			if(!vis[i])
				ans+=(double)(down[i]*son[i]+up[i])/(son[i]+1);
			else	ans+=(double)(down[i]*son[i]+up[i]*2)/(son[i]+2);
		}
		printf("%.5f",ans/n);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值