点分治教程:
例题
给定一棵带权树,显然共有N*(N-1)/2条边,问:第k小的边边长多长?
N<=10000.
题解:
这道题直接上手做实在是太难了,需要逐步拆分。
首先,问题是第k小的边的边长,这个问题不好解,但是转换一下问题,二分第k小的边长T,然后判断这棵树中<=T的路径有多少条,这个能稍微好做一下,至少变成了一个统计性问题。
二分后题目变成:
给定一棵带权树,显然共有N*(N-1)/2条边,问:<=T的边有多少条(POJ1741)。
显然的,对于整个树的根来说,路径只有两种:
1.经过根的路径
2.不经过根的路径
显然,不经过根的所有路径,都单独属于某个根的子树中。
因此,我们完全可以这么做此题:
void solve(int i)//表示统计以i为根的这棵树中的满足题目要求的路径个数。
{
work(i); //统计所有经过根节点i的满足要求的路径个数。
delete(i); //把节点i从整棵树中删掉,这样i的所有儿子就形成了不同的子树
for (i的任何一个儿子j)
{
if (j属于i的某个子树中)
solve(j); //统计以j为根节点的子树中满足要求的路径个数
}
}
例如:共有一颗树,联通情况如下:
1
2————|————3
4——|——5 6——|——7
那么,首先调用solve(1),处理所有经过1的路径,然后把1删掉,调用solve(2),solve(3)分别处理,solve(2)处理完后调用了solve(4),solve(5),solve(3)以此类推。
这么做显然是明智的,把一个规模为n的问题,通过计算一个比较简单的子问题,转换成了两个规模为2n的问题。
如果计算这个比较简单的子问题的复杂度是nlogn的话,那么总的复杂度就是nlognlogn。
然而,这么做有一个反例:
1——2——3——4——5——6——7
首先调用solve(1),然后调用solve(2),然后调用solve(3)…
如此一来复杂度退化成了n*nlogn
为了避免这一种退化,我们可以人为的规定每棵子树的根,对于上述问题:
首先调用solve(4),然后两颗子树就是1——2——3,5——6——7,再分别调用solve(2),solve(6)。
这样,最多经过logn层,势必把所有的节点都处理完毕了。
而每棵子树的根很好选择,显然就是当前子树的重心!用两遍dfs可以求得。
solve的代码非常好写,如下:
void solve(int now) //表示solve以now为根的子树,此时now已经是重心
{
int u;
ans += work(now); //work(now)返回的是经过now的路径中,满足题意的数量。
done[now] =