【模版】【P3806】点分治

(7.17)早就想学点分治了……今天状态不太在线,眯一会写篇笔记来理理思路。

--------------------------------------------------------------------

  (静态)点分治是一种利用无根树性质暴力分治的思想,可以在O(nlog^2n)的复杂度下统计可带权树上的路径信息。

  像是这道例题,多组询问是否存在长度为k的路径,需要我们预处理出一个储存所有路径长度信息的桶。

  点分治的做法,就是选定一个合适的根节点,把树上的所有路径分成不重不漏的两部分来统计:

  1、经过根节点u的路径;

  2、在u某个子树中的路径。

  每次分治我们会统计出第一种路径的信息,然后递归进入u的每个子树,将第二种路径看作它的子树内的子问题来求解。

  首先,我们要选定一个合适的根节点开始分治。最理想的根节点要满足它的每个子树大小都基本一样大;于是我们就想起了重心这个好东西。

   无根树的重心u的性质:

    1、最大子树的大小最小。

    2、最大的子树大小小于等于树大小的一半。

  如果每次选定该子树的重心为根来进行分治,我们就可以保证递归的进行不超过logn层。

  1. void find_rt(int u, int pre) {  
  2.     size[u] = 1;  
  3.     int Mx = 0;  
  4.     for (int i = head[u]; i; i = edge[i].nxt) {  
  5.         int v = edge[i].to;  
  6.         if (v == pre || vis[v]) continue;  
  7.         find_rt(v, u);  
  8.         size[u] += size[v];  
  9.         Mx = max(Mx, size[v]);  
  10.     }  
  11.     Mx = max(Mx, Size - size[u]);  
  12.     if (Mx < Mn)   
  13.         root = u, Mn = Mx;  

--------------------------------------------------------------------

  接下来是分治的过程。

  针对这道题来说,我们通过一次暴力dfs统计出当前子树中每个点的路径信息(包括根自身,深度为0),然后继续很暴力地两两组合路径,然后就出问题了……

  直接合并任意两条路径是行不通的,因为这两条路径可能来自u的同一个子节点v。此时我们得到的这条不合法路径的信息把u--->v的这条边统计了两次,所以我们要再遍历一遍它的每个子树,把这些不合法路径去掉。具体的操作可以看代码,用到了容斥原理。

  1. void dfs(int u, int pre, int depth) {  
  2.     chd[++tot] = depth;  //记录每个子节点深度
  3.     for (int i = head[u]; i; i = edge[i].nxt) {  
  4.         int v = edge[i].to;  
  5.         if (v == pre || vis[v])   
  6.             continue;  
  7.         dfs(v, u, depth + edge[i].w);  
  8.     }  
  9. }  
  10. void solve(int u, int extra, bool f) {  //第三个参数表示加减
  11.     tot = 0;  
  12.     dfs(u, 0, extra);  
  13.     if (f) {  
  14.         for (int i = 1; i <= tot; ++i)  
  15.             for (int j = i + 1; j <= tot; ++j)   
  16.                 ++ans[chd[i] + chd[j]];  
  17.     } else {  
  18.         for (int i = 1; i <= tot; ++i)  
  19.             for (int j = i + 1; j <= tot; ++j)   
  20.                 --ans[chd[i] + chd[j]];  
  21.     }  
  22. }  
  23. void divide(int u) {  //分治过程
  24.     vis[u] = true;  
  25.     solve(u, 0, 1); 
  26.     for (int i = head[u]; i; i = edge[i].nxt) {  
  27.         int v = edge[i].to;  
  28.         if (vis[v]) continue;  
  29.         solve(v, edge[i].w, 0);  //第二个参数为初始的深度,保证与以u为根算出的深度统一。
  30.         Mn = inf, Size = size[v];  
  31.         find_rt(v, 0);  
  32.         divide(root);  
  33.     }  
  34. }  

  大概不管是谁看到这里都想吐槽了:这个常数大到哪里去了?每回都要多跑一遍O(n)的搜索和O(n^2)的统计,虽然复杂度没有变,但是接受不能。

  实际上分治的过程有第二种写法,但是我还没有掌握,所以今天就先更新到这里。

猜你喜欢

转载自www.cnblogs.com/TY02/p/11203163.html
今日推荐