题外话
这道题很久之前听教练讲过,当时觉得太麻烦就没写,后来拾起来的时候就发现自己不会了 QwQ (可能是因为我太菜了吧) ,于是怒肝了两个下午把它弄了出来,中间还出了一次大锅,所以留个博客以作纪念吧
题面
题面异常简单,就是求一颗基环树上的任意一点往某个方向走到头的路径期望长度。这个一看就是要用某种类似 的方法处理的(但是我一开始没看出来),所以我们可以先从简单问题入手
50分——树上dp
我们先考虑没有环的情况,那显然要用树上DP来解决——我们定义两个数组 、 分别代表从 节点向上和向下走到头的期望距离
那么 应该是相当好转移的,因为它不牵扯拐弯。做一遍 然后统计每一个儿子的 即可求出 了,转移方程用手推一下应该就能出来:
这里 是 的每一个儿子, 表示 到 这条边的长度, 表示 的儿子数量
但是 就有点难搞,因为这条路有可能往上走了几步又拐到向下,而且你还得处理每一个位置拐弯的情况。这就非常麻烦了
我们考虑,如果我们处理出了每一个点的 ,然后从上往下 ,那么我们这条路的期望会是什么样子的呢?
我们从这幅图上可以很清楚的看到:这个期望一共涉及两种方向,也就是上面的 和 $3 $。
这个 就相当于是从 的父亲 f 出发向下不经过 的所有路径的期望,可以表示为
那如果继续向上,就相当于是从 向上随便走,其实就是 了。
所以我们就可以总结出一个计算 的公式了:
这里 表示 的度数,也就是这个点连出的边数。容易看出, 。所以直接把分母改写成 即可。
最后怎么统计答案呢?参考一下上面那幅图就可以推出:
那么树上的问题就解决了
(对了我这里少说了一句,其实这样写在洛谷上是可以混过两个基环点的哈哈哈哈)
100分——基环树特殊处理
既然环上的点不多,只有20个,我们就可以轻松 找出这些点并把它们用一个类似链表的方式保存起来(就是保存每一个点的下一位和上一位)
然后我们会发现,这个环貌似对环外的 和 没有任何影响,所以我们就可以对于每一棵小树 求出
那么根据刚才的经验,我们就可以通过这个 来推算每个点的 了
我们先来说环上的 值怎么求:
我们举个例子,如果环上的点是 ,我们钦定方向为顺时针,那么对于 来说走到 的可能性就是 。从 走到 的可能性就是 ,从 走到 的可能性就是 。。。以此类推
那么我们就可以顺时针逆时针都跑一遍,然后统计答案时除以 2 即可。
表达式不太好看,所以这里就给出一个顺时针的:
逆时针同理即可。
如果觉得没看懂表达式的话,这里上一段代码和一张图帮助各位理解:
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);
}
这里 就是上面的 ,而 就是一个预处理的 。上面所说的特判其实就是从环首走了一圈又回来的状况,请各位自行推算,这里就不再赘述了
还有一个小问题就是如何处理环上各点的儿子的 。因为这些点的父节点左右有两条连边,所以这些店需要单独拿出来计算 :
注意一下这里的
统计答案的时候与之前的式子相似,树上的按照树上的统计,环上的就是上面加一个 ,下面加 就行了,也就是下面的这个式子:
还有就是这几个期望计算的时候分母都不能是 ,所以需要手动加几个特判才行。
好了多的不说了,具体细节请看代码吧:
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;
}