Codeforces 280C Game on Tree 概率dp 樹上隨機刪子樹 求刪完次數的期望
阿新 • • 發佈:2018-12-24
題意:給定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; }