【bzoj2599】[IOI2011]Race 樹的點分治
阿新 • • 發佈:2018-01-03
樹的點分治 註意 容斥 兩個 min else 自己 輸入 algorithm
題目描述
給一棵樹,每條邊有權.求一條簡單路徑,權值和等於K,且邊的數量最小.N <= 200000, K <= 1000000
輸入
第一行 兩個整數 n, k
第二..n行 每行三個整數 表示一條無向邊的兩端和權值 (註意點的編號從0開始)
輸出
一個整數 表示最小邊數量 如果不存在這樣的路徑 輸出-1
樣例輸入
4 3
0 1 1
1 2 2
1 3 4
樣例輸出
2
題解
樹的點分治
以前的做法太sb看著不爽自己把它卡掉了。。。
對樹進行點分治,能夠想到開桶維護距離當前根節點某距離的所有點中,最小的深度是多少。這樣對於每一個點 $i$ ,直接用 $deep[i]+v[k-dis[i]]$ 更新答案即可。其中 $v$ 是桶。
但是最值並不滿足可減性,因此不能容斥處理兩個點在同一個子樹內的情況。
考慮每次找兒子的過程中其實是有序的——選擇當前子樹內的節點和以前找到過的節點,這樣是不重不漏的。
因此枚舉所有子樹,先統計子樹貢獻,然後再加到桶中。最後動態還原桶並分治子樹部分。
註意:根節點的dis為0,但是樹中存在0權邊,可能會清掉v[0],因此每次都要重新賦值v[0]=0。
使勁啊復雜度 $O(k+n\log n)$
#include <cstdio> #include <cstring> #include <algorithm> #define N 200010 using namespace std; int k , head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , si[N] , ms[N] , sum , root , deep[N] , dis[N] , vis[N] , v[N * 5] , ans = 1 << 30; inline void add(int x , int y , int z) { to[++cnt] = y , len[cnt] = z , 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 calc(int x , int fa) { int i; if(dis[x] <= k) ans = min(ans , deep[x] + v[k - dis[x]]); for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]] && to[i] != fa) deep[to[i]] = deep[x] + 1 , dis[to[i]] = dis[x] + len[i] , calc(to[i] , x); } void insert(int x , int fa) { int i; if(dis[x] <= k) v[dis[x]] = min(v[dis[x]] , deep[x]); for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]] && to[i] != fa) insert(to[i] , x); } void clear(int x , int fa) { int i; if(dis[x] <= k) v[dis[x]] = 1 << 30; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]] && to[i] != fa) clear(to[i] , x); } void solve(int x) { int i; vis[x] = 1 , v[0] = 0; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]]) deep[to[i]] = 1 , dis[to[i]] = len[i] , calc(to[i] , 0) , insert(to[i] , 0); for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]]) clear(to[i] , 0); for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]]) sum = si[to[i]] , root = 0 , getroot(to[i] , 0) , solve(root); } int main() { int n , i , x , y , z; scanf("%d%d" , &n , &k); for(i = 1 ; i < n ; i ++ ) scanf("%d%d%d" , &x , &y , &z) , add(x + 1 , y + 1 , z) , add(y + 1 , x + 1 , z); for(i = 1 ; i <= k ; i ++ ) v[i] = 1 << 30; ms[0] = sum = n , getroot(1 , 0) , solve(root); if(ans == 1 << 30) puts("-1"); else printf("%d\n" , ans); return 0; }
【bzoj2599】[IOI2011]Race 樹的點分治