树上路径统计——点分治の板子

《我TM才不想学点分系列》

话说某一天ATP和它的小伙伴们学了一个叫做dsu on the tree的东西。。然后发现这个玩意儿好方便啊还跑得很快。。。好像不但可以统计子树节点还可以统计树上路径啊。。然后ATP开心地想我是不是可以不用学点分了。。

然而几个小时以后ATP它们发现用这个东西做树上路径问题非常恶心。。再几个小时之后ATP它们又发现一个用dsu on the tree写起来恶心到爆的路径统计题。。然后。。。。。。

zyf2000(毫无波动):我写点分。

ATP(黑人问号):???

没错点分治这种东西如果用来做树上路径统计问题的话,每次只需要考虑某一棵单独的子树,进一步地说,可能只需要考虑某一棵单独的子树中经过根节点的路径情况。所以处理起来有其他算法难以替代的方便之处。。

有一篇讲树分治的论文:2009年 漆子超《分治算法在树的路径问题中的应用》

(UPD-半小时后)zyf2000:我TM现在波动很大啊我好像写错了= =
(UPD-一小时后)zyf2000:MD我T了。
(UPD-五分钟后)zyf2000:MD我不用map了我改成二分。
(UPD-五分钟后)zyf2000:我TM点分写错了原来map也是可以过的。。

把树切成一块一块的吧

点分治的基本过程是对于当前处理的一棵树,从中选择一个点,这个点能够把这棵树分成尽量平均的好多棵更小的子树。然后就相当于有了很多更小的子问题,就可以分治下去求解。

点分治保证时间复杂度的关键就是选择的点能够把这棵树分得尽量平均,也就是分完了以后规模最大的那棵子树是所有划分方案中最小的。这样的话设原来的树规模为size,分完了以后规模最大的子树不会超过size/2。也就是说每次分治下去的子问题规模至少是砍去一半的。那么也就是说这样递归分治的层数不会超过log层,而因为每层处理的子树互不相交,每层总的复杂度是 O(n) 的。如果不考虑计算过程中额外的复杂度,点分治的总复杂度就是 O(nlogn) 的。

那么关键就是如何找到这样一个最优的划分点。因为这个划分点能够把树切割得尽量平均,所以把这个点叫做树的重心。

找重心的过程和树链剖分的过程是相似的,每次dfs求出子树的size,然后假设选当前这个点为重心,那么它能够切割成的块块们一部分是它的子树(下图①②),还有一部分是整棵树除去它的那一部分(下图③),这一部分可以通过用总的树的大小N来减去它的子树大小来得到。

树上路径统计——点分治の板子_第1张图片

找重心的代码是这样的:

void getroot(int u,int fa,int N){
    size[u]=1;son[u]=0;
    for (int i=p[u];i!=0;i=nxt[i])
      if (a[i]!=fa&&vis[a[i]]==false){
          getroot(a[i],u,N);
          size[u]+=size[a[i]];
          son[u]=max(son[u],size[a[i]]);
      }
    son[u]=max(son[u],N-size[u]);
    if (son[u]

点分治的工作过程

找到重心以后就开始分治。以重心为根来dfs这棵子树,先枚举所有跟root相连的边,找到它划分出的所有子树。这里要注意可能有一条边是连着这棵子树之外的点的,这条边不能走否则就跑出当前子树之外了。比如上图中处理子树①的时候,连着黄色节点的那条边是不能走的。

这样的话就需要把子树外的点打上不能访问的标记。可以发现如果想要出这棵子树必须要经过一开始设定的根节点也就是上图的黄色节点,所以只要把黄色节点打上一个不能出去的标记就能把它的路“堵”在当前子树之内了。

void work(int u){
    ans+=calc(u);vis[u]=true;
    for (int i=p[u];i!=0;i=nxt[i])
      if (vis[a[i]]!=true){
          root=0;
          getroot(a[i],0,size[a[i]]);
          work(root);
      }
}

点分的主过程基本上就长上面那个样子。calc过程视具体的题目而定,而设计这个过程就是点分治的关键点和难点所在。

例题

  • POJ1741 Tree
  • BZOJ3365 路程统计
  • BZOJ2152 聪聪可可
  • BZOJ2599 Race
  • POJ2114 Boathers

上面几个都是比较简单的板子题啦。用到了点分治中常见的补集转化思想,也就是要求路径两个端点不在同一棵子树中的点对数目的时候,先求出子树中所有符合要求的点对数目,再减去在同一棵子树中的点对数目。而后者可以在枚举子树的时候顺便求出来。
这类题目需要发现题目中要求的路径的一些性质,比如可以排序以后利用单调性或者用排列组合的原理求解之类的。

  • BZOJ3697 采药人的路径
  • Codeforces716E Digit Tree

好像是需要想一想的题。。

  • BZOJ3784 树上的路径

这个题和普通的点分思路有所不同,不是先求出所有的再减去不合法的而是直接构造合法答案。然而这题如果姿势不对就会死T。。。

你可能感兴趣的:(板子们,并不是淀粉质而是点分治)