点分治是一种树分治算法
在解决树上路径满足某种属性的数量统计方面有着很大的作用
基本思想:
考虑到树上的路径对于一个点来说只有两种情况:
一是经过这个点,二是不经过这个点
对于不经过这个点的情况我们可以直接往下递归处理,主要问题就是解决经过这一个点的路径
如果一条路径要经过这个点,
那么ta必然是由两条在这个点两边不同子树中到这个点的路径组合而成
具体流程
First . 选取一个点,将无根树变成有根树
为了使每次的处理最优,我们通常要选取树的重心
树的重心,在之前的文章中有过介绍:
何为“重心”,就是要保证与此点连接的子树的结点数最大值最小
简单说一下重心求法:
- 一次dfs,算出以每个点为根的子树大小
- 记录以每个节点为根的最大子树大小
- 判断:如果以当前结点为根更优,就更新当前根
void getroot(int now,int fa)
{
size[now]=1;
pre[now]=fa;
f[now]=0;
int maxx=0;
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa&&!vis[way[i].y]) //避免陷入死循环
{
getroot(way[i].y,now);
size[now]+=size[way[i].y];
f[now]=max(f[now],size[way[i].y]); //最大子树
}
f[now]=max(f[now],sum-size[now]); //这个点连接的最大连通块
if (f[now]<f[root]) root=now;
}
Second . 处理连通块中通过根节点的路径
注意,是通过根节点的路径,所以后面要去掉同一子树内部的路径,即去重
Third . 标记根节点
相当于处理后,将根节点从子树中删除
Fourth . 递归处理当前点为根的每棵子树
int solve(int x)
{
vis[x]=1; //将当前点标记
for (int i=st[x];i;i=way[i].nxt)
if (!vis[way[i].y])
{
root=0; //初始化根
ans=way[i].y; //初始化sum
getroot(x,0); //找连通块的根
solve(way[i].y); //递归处理下一个连通块
}
}
int main()
{
build(); //建树
sum=f[0]=n; //初始化sum
root=0; //初始化根结点
getroot(1,0); //找重心
solve(root); //点分治
}