点分治总结
概念
点分治可以:
- 处理树上路径问题(通常与“树上任意两点之间的路径”有关)(如树上距离、树上路径边数等)
- 处理树上可二分型问题:使用点分治优化一步步走的过程。(如树的重心)
点分治通过不断找树的重心并删除,划分成若干个子树,在子树内再找重心继续递归。每个子树内分别求解答案。
复杂度为\(\mathcal{O}(n\log n)\)(还取决于\(calc\)函数的复杂度,\(calc\)函数的复杂度最大为\(\mathcal{O}(sz\log sz)\))
代码分析
注:以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; ask_rt(x, 0); // 重算一遍size
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;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步