【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;
}

 

 

posted @ 2018-01-03 19:49  GXZlegend  阅读(405)  评论(0编辑  收藏  举报