• poj-1741 (点分治模板)


    题目

    Description

    Give a tree with n vertices,each edge has a length(positive integer less than 1001).
    Define dist(u,v)=The min distance between node u and v.
    Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k.
    Write a program that will count how many pairs which are valid for a given tree.

    Input

    The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l.
    The last test case is followed by two zeros.

    Output

    For each test case output the answer on a single line.

    Sample Input

    5 4
    1 2 3
    1 3 1
    1 4 2
    3 5 1
    0 0

    Sample Output

    8

    分析

    • 题目大意就是给一棵树,每条边有一个长度,两个点之间的距离就是连接它俩的边的长度之和。求出这棵树中满足距离不大于 K 的点对个数。

    • 这基本上就是树的点分治模板题,我也是第一次打树的分治,主要有以下几个步骤:

    • 对于一棵子树的分治,首先求出它的重心, 我们目的是对这棵子树讨论所有经过重心的路径。(重心的作用是保证时间复杂度)

    • 之后,以此重心为根节点,对这棵树进行一遍深搜,得出每个节点到重心的距离dis[]

    • 之后,这棵子树中相连的路径会经过重心且对答案有贡献的点对(i,j) (i<j)就会是这样: dis[i]+dis[j] <= K 且在去除重心后,i 与 j 不在同一个联通块里

    • 不过显然要满足“不在同一个联通块里”这个条件有点突兀,于是就有了一个小技巧:

    • 先不管在不在一个联通块这个条件,算出当前这棵树的符合路径数,之后再将得出的个数减去 以重心的儿子节点为根的子树内 的 点对 路径距离(经过重心)小于等于K的个数,就行了。

    • (可能这句话有点绕,不会的话看看程序再理解理解,应该也是能领悟到的)

    • 之后,再分治一下现在这棵子树,步骤同上。

    • 一棵子树的重心其实就是你要找到一个点,使得删掉这个点后,这棵子树剩下最大(节点个数最多)联通块最小。

    • 为什么每次都要算一个重心呢?你想想,要是那一棵子树刚好是一条链(就是一整条下来),而默认的根节点又在链的端点上,那么时间不就退化到了还不如暴力的程度了?于是我们求个重心,把时间复杂度保证在了 log 级别里面,就很多了。

    程序

    #include <cstdio>
    #include <algorithm>
    #define Max 20010
    #define add(u,v,w) (To[++num]=Head[u],Head[u]=num,V[num]=v,W[num]=w)
    #define For(x) for (int h=Head[x],o=V[h]; h; h=To[h],o=V[h])
    #define Input for (int i=1,u,v,w; i<N; i++) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w)
    using namespace std;
    int N,K,root,num,ans,cnt,mins,Head[Max],To[Max],V[Max],W[Max],siz[Max],dis[Max],f[Max];
    /*
    	程序中部分变量说明
    	dis[i]	所有路径到 重心 的长度
    	siz[i]	以 i 为根节点的子树的大小(当前重心下)
    	f[i]	i 是否还存在(分治完一次后就把重心删掉了)
    	cnt		记录 dis 的个数(即路径个数)
    	root	当前子树的重心
    	maxs	当前讨论的点所有子树大小中最大值(并不是全局变量,是尝试每个重心时重新开的一个变量)
    	mins	讨论过的点的子树大小中最大的最小值(是全局变量,用来确定哪个才是重心)
    */
    
    int get_size(int x,int fa){		//返回以 x 为根的子树大小,其中 x 父节点为 fa 
    	siz[x]=1;
    	For(x) if (o!=fa && !f[o])
    		siz[x]+=get_size(o,x);
    	return siz[x];
    }
    
    void get_dis(int x,int d,int fa){	//x 到重心的长度为 d,之后继续 dfs 
    	dis[++cnt]=d;
    	For(x) if (o!=fa && !f[o])
    		get_dis(o,d+W[h],x);
    	return;
    }
    
    void dfs_root(int x,int tot,int fa) {
    	//求目标子树的重心(要求除去 x 点时,它的 maxs 值最小,那么 x 就是这棵子树的重心了),其中 tot 是这棵子树的总大小(节点个数) 
    	int maxs=tot-siz[x];	//这棵子树中x 父亲那一支先赋给 maxs 
    	For(x) if (o!=fa && !f[o]){
    		maxs=max(maxs,siz[o]);
    		dfs_root(o,tot,x);
    	}
    	if (maxs<mins){
    		mins=maxs;
    		root=x;
    	}
    	return;
    }
    
    int work(int x,int d) {
    	//返回以 x 为根的子树内长度小于等于 K 的路径数(两个端点都在子树内) 
    	//其实 d 在这里用处只有一个,是在做减法时方便把重心的儿子节点的 dis 先弄好,你也可以在分治的时候弄,不过就稍微有点麻烦了 
    	cnt=0;
    	get_dis(x,d,0);
    	sort(dis+1,dis+cnt+1);
    	int daan=0,i=1,j=cnt; 
    	while (i<j){
    		while (i<j && dis[i]+dis[j]>K) j--;
    		daan+=j-i;	//相当于选一条路径 i,另一条可以为 [i+1,j] 里任意一条路径,这样得到的两个点之间长度(经过重心的那条路径)肯定是小于等于 K 的 
    		i++;
    	}
    	return daan;
    }
    
    void dfs(int x){	//以 x 为重心分治一下 
    	cnt=0;
    	mins=Max;
    	get_size(x,0);
    	dfs_root(x,siz[x],0);
    	ans+=work(root,0);
    	f[root]=1;
    	For(root) if (!f[o]){		//注意这里是以重心开始 
    		ans-=work(o,W[h]);		//注意,这里 dis[o] 要先赋成 W[h](即它到重心的距离) 
    		dfs(o);
    	}
    	return;
    }
    
    int main(){
    	while(scanf("%d%d",&N,&K)!=EOF && N && K){
    		Input;
    		dfs(1);
    		printf("%d
    ",ans);
    				
    		num=ans=0;
    		for (int i=1; i<=N; i++) Head[i]=f[i]=dis[i]=0;
    	}
    	return 0;
    }
    
    

    时间复杂度

    • 我们注意到,对于一棵大小为 n 的树,重心选好后,它的分支大小都是不大于 $frac{n}{2} $ 的,那么每次分治后,联通块的大小会至少减少一半,因此最多递归分治 $log_2 n $ 层,每次分治都会把整棵树的点都讨论一次,于是总的时间复杂度就是:

    [nlog_2n ]

    提示

    • 开始我写的时候sort的边界弄错了,结果一直WA,要注意一下这些细节问题。
  • 相关阅读:
    深究AngularJS——自定义服务详解(factory、service、provider)
    学习AngularJs:Directive指令用法(完整版)
    理解Angular中的$apply()以及$digest()
    javascript关闭网页的几种方法
    grunt
    原生ajax
    服务器返回JSON,IE出现下载问题
    设计模式学习笔记——Mediator中介者模式
    Html.Partial
    SAML
  • 原文地址:https://www.cnblogs.com/hehepig/p/6685546.html
Copyright © 2020-2023  润新知