题目链接:传送门
题意:
给定一个以1为根的树我们知道了所有节点的深度,然后我们从中选出不少于2个节点,那么一共有2^n-1-n种方案,设f为所选节点的深度的最大值,g为所选节点的深度的次大值,然后求表达式 f*g/(f+1)/(g+1)的期望。
分析:
首先肯定要预处理出所有节点的深度,然后再来考虑没两个节点对所有节点的贡献。我们将所有节点的深度按从大到小排序设第i个节点是当前所选的最大值,第j(j>i)个节点是当前所选的次大值,那么这时所有的方案数为
2^(n-j) 那么这两个节点的共线就是 2^(n-j)/(2^n - n -1) *dep[i]*dep[j]/(dep[i]+1)/(dep[j]+1)。继续分析,由于题目要求的精度的误差是1-e6。
2^(n-j)/(2^n - n -1) ==> 1/[2^j - (n+1)/2^(n-j)]当j比较大的时候这个式子的值就特别小,对最终所求的答案的影响不大,因此我们可以只考虑j在100以内的点对对答案的贡献。
代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int maxn = 1e5+10;
vector<int >vc[maxn];
int dep[maxn];
void init() {
for(int i=0; i<maxn; i++)
vc[i].clear();
}
void dfs(int u,int pre,int dept) {
dept++;
dep[u]=dept;
for(int i=0; i<vc[u].size(); i++) {
int to = vc[u][i];
if(to!=pre)
dfs(to,u,dept);
}
}
int n;
struct cmp {
bool operator()(const int & a,const int & b) {
return a>b;
}
};
double calc(int x){
if(x>70) return 0;
double ans = n+1;
while(x--) ans/=2.0;
return ans;
}
int main() {
while(~scanf("%d",&n)) {
init();
for(int i=2; i<=n; i++) {
int x;
scanf("%d",&x);
vc[x].push_back(i);
vc[i].push_back(x);
}
dfs(1,0,0);
sort(dep+1,dep+n+1,cmp());
double ans = 0;
double tmp = 2.0;
for(int i=2;i<=min(100,n);i++){
tmp=tmp*2;
for(int j=1;j<i;j++){
ans+=(double )dep[i]*dep[j]/(dep[i]+dep[j])/(tmp-calc(n-i));
}
}
printf("%.6lf\n",ans);
}
return 0;
}