1. 程式人生 > >【bzoj2599】[IOI2011]Race 樹的點分治

【bzoj2599】[IOI2011]Race 樹的點分治

樹的點分治 註意 容斥 兩個 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 樹的點分治