点分治,基于点的分治,处理一些路径计数问题;
其思路为:
子树结构:子树结构虽然的确是某点的一个子树,但我们讨论点分治时,相当于把这个子树摘下来,当做无根树处理;
对于一个子树结构:
- 处理子树结构中与某个单点(未必是原根)相关的路径;
- 以这个被处理的点为新根,找出她的子树,变成递归下一层要处理的子树结构;
- 递归下一层;
点分治为什么可以求解诸如"统计所有合乎某一规范的路径的相关信息"之类的问题呢?
其原因在于此类问题可以通过分治解决:
对于一棵无根树的所有路径;
可以分为经过某个单点的和不经过某个单点的两类;
统计经过这一点的路径后,
剩下其他的路径都可看做是以这一点为根后,根的所有子树各自内部的路径;
也可看做把这一点摘去后得到的森林中每个树内部的路径;
然后重复这一过程即可解决统计所有合乎某一规范的路径的相关信息;
因此点分治问题的要点是寻找一种可以统计经过某一点的路径信息的优秀算法
在一个常见的,约定俗成的写法下(就是一个“说点分治就是指她”的写法)
点分治时间复杂度可以达到O(logn*f(num(1))
(num(i)为处理第i个单点时的数据范围);
(f(i)为处理数据范围为i的单点的时间复杂度);
(条件是f(i)是线性的);
这个复杂度是因为:
在递归分治的过程中,
对于每层,其num(i)之和与num(1)是同级的,
由于f(i)(往往)是线性的,故可以认为每层的各点的f(i)之和与f(num(1))同级,
在此基础上,我们使用里一种巧妙的办法,使得分治层数不超过logn层;
于是达到了需要的效率;
所以这个神奇的方法是什么呢?
由于对于每个子树结构,我们要处理的那个单点并没有什么特殊要求
于是考虑通过限定处理哪个单点以提高效率;
处理哪个单点会影响效率么?
会的;
处理不同的单点会导致下一层的那些子树结构大小不同;
理想状态下我们希望;
不管下一层有多少个子树结构,每一个子树结构的大小都尽量小,因为这样可以是分治层数尽量少
(比如,当我们遇到菊花图时,如果第一次处理的是该树中间的那个点,那么尽管下一层要处理n-1个点,但是下一层每个子树结构的大小都只有1,这就意味着不用继续分治了,只分治了两层)
考虑把每层的子树大小的最大数量级,都变成上一层的一半;
这样就能使分治层数不超过logn时就不能再分了(因为连最大的那个子树此时的大小也是1了);
如何做到这点呢?
首先定义子树结构的重心:
在点分治中若在本层处理点i,可以使下一层处理的最大子树的节点最少,则点i为该子树结构的重心;
可以看出重心的节点最多的子树的节点数不会超过原子树结构的一半;
证明:
若重心的节点最多的子树的节点数超过了原子树结构的一半;
那么你看看以她的这个子树的根取代她作为重心的情况;
发现反而更有重心的样子;
于是这个重心不是重心;
所以重心的节点最多的子树的节点数不会超过原子树结构的一半;
于是我们用现在得到的方法重新修订、精细点分治的流程:
对于一个子树结构:
- 处理与子树结构中重心相关;
- 以重心为新根,找出她的子树,变成递归下一层要处理的子树结构;
- 递归下一层;
这就是一个常规的约定俗成的却又效率优秀的点分治啦;
然后是一些实现:
分治的主体可以由dfs实现,
但跳转时,不会是直接跳到当前点的子节点,而是跳到当前点的子树重心上;
求重心:
两遍dfs实现:
inline void dfs_size(int now,int fa){ size[now]=1; for(int i=first[now];i;i=e[i].next) if(!vis[e[i].to]&&e[i].to!=fa){ dfs_size(e[i].to,now); size[now]+=size[e[i].to]; } }
inline int dfs_root(int now,int fa,int r){ int i,root=-1,wroot; max_size[now]=size[r]-size[now]; for(i=first[now];i;i=e[i].next) if(!vis[e[i].to]&&e[i].to!=fa){ if(size[e[i].to]>max_size[now]) max_size[now]=size[e[i].to]; wroot=dfs_root(e[i].to,now,r); if(max_size[wroot]<max_size[root]||root==-1) root=wroot; } if(max_size[now]<max_size[root]||root==-1) root=now; return root; }
分治部分:
void part_dfs(int now){ int i,root; dfs_size(now,0); root=dfs_root(now,0,now); ans+=get_ans(root,0); vis[root]=1; for(i=first[root];i;i=e[i].next) if(!vis[e[i].to]){ ans-=get_ans(e[i].to,e[i].d); part_dfs(e[i].to); } }
具体见例题
例题: