点分治入门

点分治入门

转载于:https://blog.csdn.net/qq_39553725/article/details/77542223

点分治,是处理树上路径的一个极好的工具。
一般如果需要大规模处理树上路径,点分治是一个不错的选择。
这里我就来讲一讲我自己对于点分治的一点理解和感悟(帮助新手入坑……)
现在就开始吧!

1.点分治的基本思想

点分治,顾名思义就是基于树上的节点进行分治。
如果我们在深入一点呢?对于点的拆开其实就是对于树的拆开。
所以我认为点分治的本质其实是将一棵树拆分成许多棵子树处理,并不断进行。
这应该也是点分治的精髓。

2.分治点的选择

既然我们要将一个点进行分治,那么选点肯定最首要的。
思考下面一个问题:
如果树退化为一个链,
我们选取链首作为分治点,理论时间复杂度?
而如果选择链的中心,它的理论时间复杂度又是多少?
答案其实还是挺简单的。
选择链首:O( n )
选择链心:O( logn )
通过这个例子,我们不难发现:
如果选点后左右子树越大,递归层数越多,时间越慢,反之则越快。
所以我们的选点标准就出来了,而我们把有这个性质的点叫做:树的重心!

3.重心的O( n )求解<求解分治点>

正式一点定义一下重心:找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,
先贴出找重心的代码(各位结合着理解下面的内容):

void getroot(int u,int fa)
{
  sim[u] = 1; mxson[u] = 0;
  for(int i = head[u];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v]||v == fa)continue;
      getroot(v,u);
      sim[u] = sim[u] + sim[v];
      mxson[u] = max(mxson[u],sim[v]);
    }
  mxson[u] = max(mxson[u],Smer - sim[u]);
  if(mxson[u]<MX){root = u; MX = mxson[u];}
}

其中sim[i]表示以i为根节点的子树的节点数量(即子树大小),mxson[i],表示i节点为根的子树中的最大子树。
Smer为这棵树(当前处理树)的大小。MX记录当前已经找到的最小 最大子树值。

那么究竟是怎么找的呢?其实也蛮简单的。
就是暴力(当然不是正真意义上的暴力啦,毕竟人家是O(n)的)搞出所有子树的大小,找出最大子树,与原来答案比较,更新答案。
但在这里要特别注意一下这一句:

mxson[u] = max(mxson[u],Smer - sim[u]);

这一句其实使用到了容斥原理的思想(后面还会用到),求出的是经过父亲节点的子树大小。
读者们细细体会一下代码,这部分应该还是很好懂(≧▽≦)/啦啦啦。

4.点分治的实现

有了前面的基础知识我们就可以正式进入点分治的探讨了。
点分治是用递归实现的,先给出代码:

void Divide(int tr)
{
  ans = ans + solve(tr,0);
  vis[tr] = true;
  for(int i = head[tr];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v])continue;
      ans = ans - solve(v,t[i].len);
      Smer = sim[v]; root = 0;
      MX = INF; getroot(v,0);
      Divide(root);
    }
  return;
}

我们一点一点的分析。
首先对于当前点,求解经过此点的所有贡献(solve)。
ans = ans + solve(tr,0);

把当前点标记(防止陷入死循环)。
vis[tr] = true;

然后进行分治,分别处理此点的每一棵子树。
for(int i = head[tr];i;i = t[i].next)

容斥原理去除非法答案。
ans = ans - solve(v,t[i].len);
这里要着重讲一下。
对于以下这棵树:

这里写图片描述

显然A点是它的重心。
我们假设现在分治到了A点(当前点为A)
我们一开始求解贡献时,会有以下路径被处理出来:
A—>A
A—>B
A—>B—>C
A—>B—>D
A—>E
A—>E—>F (按照先序遍历顺序罗列)
那么我们在合并答案是会将上述6条路径两两进行合并。
这是注意到:
合并A—>B—>C 和 A—>B—>D 肯定是不合法的!!
因为这并不是一条树上(简单)路径,出现了重边,我们要想办法把这种情况处理掉。
处理方法很简单,减去每个子树的单独贡献。
例如对于以B为根的子树,就会减去:
B—>B
B—>C
B—>D
这三条路径组合的贡献
读者可能会有疑问,这与上面的6条路径并不一样啊。
我们再回过头来看一看这两句代码:
ans = ans + solve(tr,0);
ans = ans - solve(v,t[i].len);
注意到了吧,solve函数的第二个初始值并不相同。
我们在处理子树时,将初始长度设为连接边长,这样做就相当于个子树的每个组合都加上了A—>的路径,从而变得与上面一样。
个人认为这是点分治一个极其重要的地方,读者们一定要理解清楚。

重设当前总树大小,寻找新的分治点(重心)
Smer = sim[v]; root = 0;
MX = INF; getroot(v,0);

递归处理新的分治点
————————————————
版权声明:本文为CSDN博主「Guess_Ha」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39553725/article/details/77542223

posted @ 2020-01-17 16:06  Artoriax  阅读(106)  评论(0编辑  收藏  举报