【bzoj3451】Tyvj1953 Normal 期望+樹的點分治+FFT
題目描述
給你一棵 $n$ 個點的樹,對這棵樹進行隨機點分治,每次隨機一個點作為分治中心。定義消耗時間為每層分治的子樹大小之和,求消耗時間的期望。
輸入
第一行一個整數n,表示樹的大小
接下來n-1行每行兩個數a,b,表示a和b之間有一條邊
註意點是從0開始標號的
輸出
一行一個浮點數表示答案
四舍五入到小數點後4位
如果害怕精度跪建議用long double或者extended
樣例輸入
3
0 1
1 2
樣例輸出
5.6667
題解
期望+樹的點分治+FFT
由於期望可加,因此所求等於 $\sum\limits_{i=1}^n\sum\limits_{j=1}^nP(j在i的點分樹子樹內)$ 。
而 $j$ 在 $i$ 的點分樹子樹內,又相當於:$i$ 到 $j$ 的路徑上的所有點中(包括 $i$ 和 $j$),$i$ 是第一個選擇的。因為如果其它點先被選擇則會將 $i$ 與 $j$ 分開,使得 $j$ 不在 $i$ 的點分樹內。
這些點中,顯然每個點作為第一個選擇的點的概率都是相等的,因此概率為 $\frac 1{dis(i,j)}$ ( $dis(i,i)=1$ )。
所求轉化為求 $\sum\limits_{i=1}^n\sum\limits_{j=1}^ndis(i,j)$ ,即求距離等於每個值的點對數目。
考慮點分治,每次統計經過根節點路徑的答案。dfs一遍子樹得出距離等於每個值的點的個數,用容斥(任選兩個 - 在同一棵子樹內選兩個)的方法得出答案。
容易發現求答案的過程實際上就是自身與自身求卷積,因此使用FFT快速求解。
由於距離範圍(多項式次數)不會超過子樹大小,因此時間復雜度為 $T(n)=O(n\log n)+2(T(\frac n2)+O(\frac n2\log n))=O(n\log^2n)$
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> #define N 30010 using namespace std; const double pi = acos(-1); struct data { double x , y; data() {} data(double a , double b) {x = a , y = b;} data operator+(const data &a)const {return data(x + a.x , y + a.y);} data operator-(const data &a)const {return data(x - a.x , y - a.y);} data operator*(const data &a)const {return data(x * a.x - y * a.y , x * a.y + y * a.x);} }A[65550]; int head[N] , to[N << 1] , next[N << 1] , cnt , vis[N] , si[N] , ms[N] , sum , root , deep[N] , val[N] , tot , num[N]; inline void add(int x , int y) { to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt; } void getroot(int x , int fa) { int i; si[x] = 1 , ms[x] = 0; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]] && to[i] != fa) getroot(to[i] , x) , si[x] += si[to[i]] , ms[x] = max(ms[x] , si[to[i]]); ms[x] = max(ms[x] , sum - si[x]); if(ms[x] < ms[root]) root = x; } void getdeep(int x , int fa) { int i; val[++tot] = deep[x]; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]] && to[i] != fa) deep[to[i]] = deep[x] + 1 , getdeep(to[i] , x); } void fft(data *a , int n , int flag) { int i , j , k; for(i = k = 0 ; i < n ; i ++ ) { if(i > k) swap(a[i] , a[k]); for(j = n >> 1 ; (k ^= j) < j ; j >>= 1); } for(k = 2 ; k <= n ; k <<= 1) { data wn(cos(2 * pi * flag / k) , sin(2 * pi * flag / k)); for(i = 0 ; i < n ; i += k) { data w(1 , 0) , t; for(j = i ; j < i + (k >> 1) ; j ++ , w = w * wn) t = w * a[j + (k >> 1)] , a[j + (k >> 1)] = a[j] - t , a[j] = a[j] + t; } } if(flag == -1) for(i = 0 ; i < n ; i ++ ) a[i].x /= n; } void calc(int flag) { int i , mx = 0 , n = 1; for(i = 1 ; i <= tot ; i ++ ) mx = max(mx , val[i]); while(n <= 2 * mx) n <<= 1; for(i = 0 ; i < n ; i ++ ) A[i].x = A[i].y = 0; for(i = 1 ; i <= tot ; i ++ ) A[val[i]].x ++ ; fft(A , n , 1); for(i = 0 ; i < n ; i ++ ) A[i] = A[i] * A[i]; fft(A , n , -1); for(i = 0 ; i <= 2 * mx ; i ++ ) num[i] += flag * (int)(A[i].x + 0.1); } void dfs(int x) { int i; vis[x] = 1 , deep[x] = tot = 0 , getdeep(x , 0) , calc(1); for(i = head[x] ; i ; i = next[i]) { if(!vis[to[i]]) { deep[to[i]] = 1 , tot = 0 , getdeep(to[i] , 0) , calc(-1); sum = si[to[i]] , root = 0 , getroot(to[i] , 0) , dfs(root); } } } int main() { int n , i , x , y; long double ans = 0; scanf("%d" , &n); for(i = 1 ; i < n ; i ++ ) scanf("%d%d" , &x , &y) , add(x + 1 , y + 1) , add(y + 1 , x + 1); sum = ms[0] = n , root = 0 , getroot(1 , 0) , dfs(root); for(i = 0 ; i < n ; i ++ ) ans += (long double)num[i] / (i + 1); printf("%.4Lf\n" , ans); return 0; }
【bzoj3451】Tyvj1953 Normal 期望+樹的點分治+FFT