题目链接:点击打开链接
题意:给定n个点的一棵树
每次操作随机选任意一个点,把这个点和这个点的子树删去。
当把所有点删去则停止。
问操作次数的期望。
题解引用自:点击打开链接
删除的规则拥有一个非常好的性质:对于任意(u,v),选择u会导致删除v,那么选择u会删除的点集合一定包含选择了v以后会删除的点集合。
我们考虑换一种方式来实现删除的过程:
产生一个随机的1-n的排列P,从前往后依次尝试删除这些点,如果当前点已经被删除,就什么都不干,否则把次数+1,删除这个点以及他的所有后代。
通过这种方式产生的实际删除序列与题目原来规定的方式的概率分布是相同的。这一点是比较显然且非常容易证明的。
如果我们自己YY出来的过程进行了一半,排列P还有一个后缀等待解释,这个后缀中1到n号是实际仍然留在树中的点,n+1到 n+k号是实际已经不存在的点。那么1号点是下一次被选择的点的概率是
由于之前我们发现的性质,上述过程也可以这样说:
产生一个随机的1-n的排列P,从前往后删除这些点,如果当前点未删除,把次数+1,之后无条件删除这个点以及他的所有后代。
(与原先那个的区别,尝试YY几个类似的删东西的题目,然后尝试用这种方法做就能体会了。。)
这有什么用呢?由期望的线性性,我们最后要计算的是E(总操作次数)=sigma{E([每个结点u是因为选中了自己而删去的])},之后那个谓词成立当且仅当在我们生成的排列P中,u出现在他所有的祖先之前,因此这一项的期望值就是u在树中深度的倒数。把所有深度倒数加起来就是答案。
#include<bits/stdc++.h> template <class T> inline bool rd(T &ret) { char c; int sgn; if(c=getchar(),c==EOF) return 0; while(c!='-'&&(c<'0'||c>'9')) c=getchar(); sgn=(c=='-')?-1:1; ret=(c=='-')?0:(c-'0'); while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0'); ret*=sgn; return 1; } template <class T> inline void pt(T x) { if (x <0) { putchar('-'); x = -x; } if(x>9) pt(x/10); putchar(x%10+'0'); } using namespace std; const int N = 100005; struct Edge{ int to, nex; }edge[N<<1]; int head[N], edgenum; void add(int u, int v){ Edge E = {v, head[u]}; edge[edgenum] = E; head[u] = edgenum++; } void init(){memset(head ,-1, sizeof head); edgenum = 0;} int dep[N], n; void dfs(int u, int fa, int deep){ dep[u] = deep; for(int i = head[u]; ~i; i = edge[i].nex){ int v = edge[i].to; if(v == fa)continue; dfs(v, u, deep+1); } } void input(){ init(); for(int i = 1, u, v; i < n; i++) { cin>>u>>v; add(u, v); add(v, u); } } int main(){ ios::sync_with_stdio(false); while(cin>>n){ input(); dfs(1,1,1); double ans = 0; for(int i = 1; i <= n; i++) { ans += 1.0/dep[i]; } printf("%.10f\n", ans); } return 0; }
时间: 2024-12-15 12:20:55