bzoj 2878: [Noi2012]迷失游乐园
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
1 2 3
2 3 1
3 4 4
Sample Output
【样例解释】样例数据中共有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
这个题真的好迷啊,但是这个题的暴力分是真的没得说。。。
%烂lcf2000。。。
该题要求的实际上就是从每个点出发走到一个叶子结点的长度的期望(不能走回头路)。。。
首先考虑树的情况,考虑和求树的直径一样的dp方法,先处理子树内部的,再处理子树外的。。。
设down[i]表示从i这个点往子树内部走到叶子结点的长度的期望,son[i]表示i这个节点的儿子节点个数。。。
考虑期望的含义就是所有情况的总和除以总情况(就是对所有情况求平均)
我们可以得到转移:
这样我们就把每个点往下走到叶子节点的期望就出来了。。。
然后我们考虑他通过他的父亲节点走出去然后再到子树外的叶子节点的期望。。。
设up[i]为i节点,往子树外面走到叶子节点的期望长度。。。
我们考虑向上走的两种情况:
第一种,节点i走到父亲f,然后再走到f的子树内部(除去i自身的子树)的其余叶子节点;
这种情况的总长度为:
第二种,节点i走到父亲f,然后从f继续往上走,那么这种情况只有一种:
即为:
那么总共有种情况(f的子树内部要除去i的子树,然后再加上往上走的)
那么我们可以得到转移:
恩,还是很妙的。。。
每个点最后的期望就是:
由一些关于根节点的小特判,以及上述所有的除法操作一定要判分母是否为0。。。
然后考虑环套树的情况,我们可以想象成是一个根结点为一个环的树,也可以看成环上的每个点都长出去一棵树。。。
那么和树的情况唯一不同的地方就在于,一棵树上的点到了环上之后,可以通过环,走到别的树上去。。。
但是环上的点很少,那么我们可以暴搞。。。
那么我们可以对于环上的每个点先都把以它为根长出去的树的down值求出来,这个的求法和树的情况完全一样。。。
接下来我们就要处理到了环上之后再走到环上别的点的树上的情况。。。
那么环上有两种走法,一种是顺时针,一种是逆时针。。。
环上的点编号为1、2、3、4、5,那么对于1来说,顺时针走的话,
走到2的概率为1,
走到3的概率为,
走到4的话就再乘……逆时针走的话同理,用g表示到这个点的概率(g的初值为0.5,顺时针和逆时针两种)
同时我们沿着环每走到一个位置就加上从这里向外向树走的期望长度,,设d为在环上已经走的路径。。。
转移的仍是用总和/总情况。。。
即
(注意绕一圈走到头的地方与之前的不一样,因为出发点不可能经过两次,
即
最后我们改一下前面树dp的一些定义,用f[i]表示i的父节点个数,默认环上的点i的f[i]=2,然后有的跟父亲的转移稍微改一下。。。
这个题果然是迷失心智。。。
// MADE BY QT666 #include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=300050; int cir[N],b[N],dis[N],son[N],f[N],fa[N],vis[N],tt; int head[N],to[N],nxt[N],w[N],dfn[N],cnt,n,m,len; double up[N],down[N],ans; void lnk(int x,int y,int z){ to[++cnt]=y,nxt[cnt]=head[x],w[cnt]=z,head[x]=cnt; to[++cnt]=x,nxt[cnt]=head[y],w[cnt]=z,head[y]=cnt; } void dfs_down(int u){ int tot=0;vis[u]=1; for(int i=head[u],v;v=to[i],i;i=nxt[i]) if(!vis[v]) dis[v]=w[i],dfs_down(v),tot++,down[u]+=down[v]+w[i]; if(tot) down[u]/=tot;son[u]=tot;vis[u]=0; } void dfs_up(int u,int fa){ vis[u]=1;if(fa) f[u]=1; if((son[fa]-1+f[fa])&&fa) up[u]+=(up[fa]*f[fa]+son[fa]*down[fa]-down[u]-dis[u])/(son[fa]-1+f[fa]); for(int i=head[u],v;v=to[i],i;i=nxt[i]) if(!vis[v]) up[v]+=w[i],dfs_up(v,u); } void getrt(int rt,int x){ for(int u=x;u!=fa[rt];u=fa[u]) cir[++len]=u,b[len+1]=dis[u],vis[u]=1,f[u]=2; } void Tarjan(int x,int ff){ dfn[x]=++tt;fa[x]=ff; for(int i=head[x];i;i=nxt[i]){ int v=to[i]; if(!dfn[v]) dis[v]=w[i],Tarjan(v,x); else if(dfn[v]>dfn[x]) b[1]=w[i],getrt(x,v); } } void solve_cir(int x,int j,int step){ double g=0.5,d=0; for(int i=1;i<len;i++){ if(step==-1) d+=b[j];j+=step; if(j==0) j=len;if(j==len+1) j=1; if(step==1) d+=b[j]; if(i==len-1) up[x]+=g*(down[cir[j]]+d); else up[x]+=g*(down[cir[j]]+d)*son[cir[j]]/(son[cir[j]]+1); g/=(son[cir[j]]+1); } } void work_cir(){ Tarjan(1,0); for(int i=1;i<=len;i++) dfs_down(cir[i]),vis[cir[i]]=1; for(int i=1;i<=len;i++) solve_cir(cir[i],i,1),solve_cir(cir[i],i,-1); for(int i=1;i<=len;i++) dfs_up(cir[i],0); } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int x,y,z;scanf("%d%d%d",&x,&y,&z);lnk(x,y,z); } if(m==n-1) dfs_down(1),dfs_up(1,0); else work_cir(); for(int i=1;i<=n;i++){ ans+=(down[i]*son[i]+up[i]*f[i])/(son[i]+f[i]); } printf("%.5f\n",ans/n); return 0; }