概念
点分治是处理树上路径的一个好方法。(如树上距离、树上路径边数等)
点分治通过不断找树的重心,划分成若干个子树,在子树内再找重心继续递归。每个子树内分别求解答案。
复杂度为(O(NlogN))(还取决于(calc)函数的复杂度,(calc)函数的复杂度最大为(O(SZlogSZ)))
代码分析
注:以P4178 Tree为例(求出树上两点距离小于等于(k)的有序点对数量)
void ask_rt(int x, int fa) // 找到子树的重心
{
sz[x] = 1;
int mx = 0;
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i];
if (vis[y] || y == fa) continue;
ask_rt(y, x);
sz[x] += sz[y];
mx = max(mx, sz[y]);
}
mx = max(mx, sum - sz[x]);
if (mx < mn)
{
mn = mx;
rt = x;
}
return;
}
void work(int x, int fa, int s) // 算出以当前子树重心为根x和子树内其他节点y之间的所有距离
{
sta[ ++ top] = s;
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i], z = w[i];
if (vis[y] || y == fa) continue;
work(y, x, s + z);
}
return;
}
int calc(int rt_, int s) // 找到当前子树内的答案
{
top = 0;
work(rt_, 0, s);
sort(sta + 1, sta + top + 1);
int cnt = 0;
for (int i = 1; i < top; i ++ )
{
int pos = upper_bound(sta + i + 1, sta + top + 1, k - sta[i]) - sta;
cnt += (pos - i - 1);
}
return cnt;
}
void part(int x) // 进行点分治
{
vis[x] = 1;
res += calc(x, 0);
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i], z = w[i];
if (vis[y]) continue;
res -= calc(y, z); // 去重,防止重复计算。因为路径会在calc(x,0)和calc(y,0)中都计算一次
mn = 1e9, rt = 0, sum = sz[y];
ask_rt(y, 0), part(rt);
}
return;
}