CF697D-Puzzles
题目
一棵树,从根节点开始dfs,每层以随机顺序进入每个子节点,问走到每个点的时候期望经过了多少个点。
(这里经过多少个点指的是经过多少个不同的点,即经过一个点多次算一个)
(其实这个题不如说求期望dfn序)。
\(n\le 10^5\)。
分析
一个很明显的思路就是:\(f[x]=1+f[fa]+绕来绕去期望经过的点个数\) 。从上往下dfs,问题就转化成了如何求每一个点\(x\)进入其子节点\(v\)之前期望经过的点个数。设绕来
绕去期望经过点个数为\(g[x]\) ,它的父亲有\(n\)个子节点。
计算这个东西有两种思路:
思路1
注意到这是一个dfs,所以我们如果进了一颗子树,那么它会走完整个子树再出来,而这个子树之前是没有走过的,即点数增加了\(\text{size}[v]\) 。(\(v\)为与\(x\)同父亲的点)
这样我们就可以通过枚举之前走进了多少个子树来求:
\[\begin{aligned}
g[x]&=\sum _{i=1}^{n-1} 任意不含x的i个的size的和*\frac{1}{n}*\frac{1}{n-1}*\cdots*\frac{1}{n-i} \\
&=\sum _{i=1}^{n-1} \frac{(n-i-1)!}{n!}\sum _{v\ne x}i*size[v]* A_{n-2}^{i-1} \\
&=\sum _{i=1}^{n-1} \frac{(n-i-1)!}{n!}\sum _{v\ne x}i*size[v]* \frac{(n-2)!}{(n-i-1)!} \\
&=\sum _{v\ne x}size[v] \sum _{i=1}^{n-1}\frac{(n-2)!}{n!} \\
&=\frac{1}{2}\sum _{v\ne x}size[v]
\end{aligned}
\]
思路2
把从\(x\)父亲进入这一层的顺序列出来,所有情况是它的全排列。\(x\)前面有\(i\)个数的概率为\(\frac{1}{n}\), 前面\(i\)个数的和的期望为\(\frac{i\sum _{v\ne x}size[v]}{n-1}\),所以所有情况为
\[\begin{aligned}
g[x]&=\frac{1}{n}*\frac{\sum _{i=1}^{n-1}i\sum _{v\ne x}size[v]}{n-1} \\
&=\frac{1}{2}\sum _{v\ne x}size[v]
\end{aligned}
\]
代码
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxn=1e5+1;
vector<int> g[maxn];
double f[maxn];
int size[maxn];
inline void add(int x,int y) {g[x].push_back(y);}
int Size(int x) {
int &sz=size[x]=1;
for (int v:g[x]) sz+=Size(v);
return sz;
}
void dfs(int x) {
for (int v:g[x]) f[v]=1.0+f[x]+(double)(size[x]-size[v]-1)/2.0,dfs(v);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
#endif
int n=read();
for (int i=2;i<=n;++i) add(read(),i);
f[1]=1;
Size(1);
dfs(1);
for (int i=1;i<=n;++i) printf("%.2lf%c",f[i]," \n"[i==n]);
return 0;
}