今天做了一道点分治的题目,所以就去网上学了一下。
相信大家都听说过“分治”吧,分治就是“分而治之”一般是把n分成2份,然后再对每一份进行相同的操作,最后合并起来。
而点分治,一般情况下是在一棵树上面进行分治,和普通的分治大同小异。
先看一道例题
【题意】
给定一个有N个点(编号1,2,…,N)的树,每条边都有一个权值(不超过1000)。
树上两个节点x与y之间的路径长度就是路径上各条边的权值之和。
求长度不超过K的路径有多少条。
poj 1741 – tree
【输入格式】
输入包含多组测试样例。
每组测试样例的第一行包含两个正整数N和K。
接下来N-1行,每行包含三个正整数u,v,l,表示节点u与v之间存在一条边,且边的权值为l。
当输入样例N=0,K=0时,表示输入终止,且该样例无需处理。
【输出格式】
每个测试样例输出一个结果。
每个结果占一行。
【数据范围】
N≤10000,K≤10^7
【输入样例】
5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0
【输出样例】
8
先是考虑暴力的解法,直接枚举每一个点,求lca,然后判断是否<=k,可以得10分
仔细想想,对于一个点来说,长度<=k的路径无非就只有两种:
1.经过该点
2.不经过该点
所以我们采取点分治的做法,先选1作为根节点,求出经过1且<=k的路径条数,然后删掉1,再对1的每个儿子进行重复操作
那么我们怎么求出路径的条数呢
设d[i]为i到根节点root的距离,其中d[root]=0
搜索求出d,对d从小到大排序
然后我们令指针l = 1, r = tp (tp为d中,点的个数)
如果 d[l] + d[r] <= k ,结果就加上r-l+1,然后l++
否则r- -
但是这样会出现一个问题,比如son是root的一个儿子,在son的子树中,有很多个加起来满足<=k的,而这些都不经过u,怎么办???
容斥原理!!!
对于每个儿子,令它的d不变,统计一次<=k的个数,从ans里面减去这个数就好了。
这样可以取得70分
为什么只有70呢
举个例子吧,如果树退化成链的话,这个做法就和暴力没有区别,时间复杂度O(n2logn)O(n^2logn)O(n2logn)
怎么办呢,因为这是一棵无根树,每一个点成为根节点都不会影响结果,所以我们每次都从当前的树种求出它的重心作为root,这样的话,无论树怎么样,都只有lognlognlogn层,时间复杂度O(nlog2n)O(nlog^2n)O(nlog2n)
这是形象的搜索过程
参考代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10006;
int n, m, k, all, ans, root;
/