zoukankan      html  css  js  c++  java
  • 【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+nlog 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
    " , ans);
    	return 0;
    }
    

     

  • 相关阅读:
    LeetCode题解——两数之和
    题解LeetCode——回文数
    汇编语言入门教程
    python基础--局部变量与全局变量
    linux--基础知识1
    python基础--函数
    字符串format函数使用
    字符串的拼接
    python基础--6 集合
    python基础--5字典
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/8185376.html
Copyright © 2011-2022 走看看