【bzoj2599】[IOI2011]Race 树的点分治
题目描述
给一棵树,每条边有权.求一条简单路径,权值和等于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; }