zoukankan      html  css  js  c++  java
  • 【P4178】Tree——点分治

    (题面来自luogu)

    题目描述

    给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K

    输入格式

    N(n<=40000) 接下来n-1行边描述管道,按照题目中写的输入 接下来是k

    输出格式

    一行,有多少对点之间的距离小于等于k

      原本是点分治的模版题,从昨晚调到今晚……这里记录下点分治实现时需要注意的几个细节。

    1、分治过程中递归子树大小的确定

      以下是点分治过程的核心函数,其中cur表示以u为根进行分治的树的大小。

    1. void Divide(int u) {   
    2.     vis[u] = true;  
    3.     ans += Solve(u, 0);  
    4.     int tcur = cur;  
    5.     for (int i = head[u]; i; i = edge[i].nxt) {  
    6.         int v = edge[i].to;  
    7.         if (vis[v]) continue;  
    8.         ans -= Solve(v, edge[i].w);  
    9.         Mn = inf;  
    10.         //cur = size[v];  
    11.         cur = size[u] > size[v] ? size[v] : tcur - size[u];  
    12.         Find_rt(v, u);  
    13.         Divide(root);  
    14.     }  
    15. }  

      重点在第10、11行递归子树大小确定的两种写法,其中第11行未注释的版本是正确的。考虑到我们每次在当前树中选重心为根进行分治,那么u并不一定是该树在搜索树意义下的根节点。也就是说,u的子节点v有可能是u在搜索树上的父亲,因此在确定递归子树大小时加入一个特判。因为cur的值会因为遍历先前的v而改变,我们在第4行用一个新变量记录当前树的大小。这个就是调了一天的锅的出处

      (不过据说不加这个判断复杂度也不会劣化……貌似还有人证明了,不过保证正确性显然是好的)

    2、关于点分治两种写法的优劣

      点分治不同写法的讲解请见我的博客:https://www.cnblogs.com/TY02/p/11203163.html

      之前认为用容斥算两遍的做法常数过大,比较起来把子树分开互相统计更好。实际上第二种做法有它的局限性:例如在这个题中,暴力枚举每条路径会T飞,我们只能把u子树中所有的节点深度都统计一遍,排序后利用单调性用双指针统计答案。这就暴露了分子树统计的劣势,它只可以把子树中两点不重不漏地两两枚举、组合路径信息,无法在其中嵌套别的操作。容斥的优点在于它把所有的节点信息一次性统计出来,适合类似本题利用数据单调性排序来统计的情形。这个题也不排序也可以用权值树状数组来做,复杂度相同,常数因为要清空数组会大一些。

    完整代码:

    1. #include <iostream>  
    2. #include <cstdio>  
    3. #include <cstring>  
    4. #include <algorithm>  
    5. #define BUG puts("$$$")  
    6. #define rint register int  
    7. #define maxn 40010  
    8. typedef long long ll;  
    9. using namespace std;  
    10. const int inf = (int)1e9;  
    11. template <typename T>   
    12. void read(T &x) {  
    13.     x = 0;  
    14.     char ch = getchar();  
    15. //  int f = 1;  
    16.     while (!isdigit(ch)) {  
    17. //      if (ch == '-') f = -1;   
    18.         ch = getchar();  
    19.     }  
    20.     while (isdigit(ch)) {  
    21.         x = x * 10 + (ch ^ 48);  
    22.         ch = getchar();  
    23.     }  
    24. //  x *= f;  
    25. }  
    26. int n, k;  
    27. int ans = 0;  
    28. int head[maxn], top;  
    29. struct E {  
    30.     int to, nxt, w;  
    31. } edge[maxn << 1];  
    32. inline void insert(int u, int v, int w) {  
    33.     edge[++top] = (E) {v, head[u], w};  
    34.     head[u] = top;  
    35. }  
    36. bool vis[maxn];  
    37. int size[maxn], root, Mn, cur;  
    38. void Find_rt(int u, int pre) {  
    39.     size[u] = 1;  
    40.     int Mxson = 0;  
    41.     for (int i = head[u]; i; i = edge[i].nxt) {  
    42.         int v = edge[i].to;  
    43.         if (v == pre || vis[v]) continue;  
    44.         Find_rt(v, u);  
    45.         size[u] += size[v];  
    46.         Mxson = max(Mxson, size[v]);  
    47.     }  
    48.     Mxson = max(Mxson, cur - size[u]);  
    49.     if (Mn > Mxson)  
    50.         root = u, Mn = Mxson;  
    51. }  
    52. int chd[maxn], tot;  
    53.   
    54. void calc(int u, int pre, int d) {  
    55.     chd[++tot] = d;  
    56.     if (d >= k) return;  
    57.     for (int i = head[u]; i; i = edge[i].nxt) {  
    58.         int v = edge[i].to;  
    59.         if (v == pre || vis[v]) continue;  
    60.         calc(v, u, d + edge[i].w);  
    61.     }  
    62. }  
    63. int Solve(int u, int extra) {  
    64.     int ret = 0;  
    65.     tot = 0;  
    66.     calc(u, 0, extra);  
    67.     sort(chd + 1, chd + 1 + tot);  
    68.     rint l = 1, r = tot;  
    69.     while (l < r)  
    70.         chd[l] + chd[r] <= k ? (ret += (r - l), ++l) : (--r);  
    71.     return ret;  
    72. }  
    73. void Divide(int u) {  
    74.     vis[u] = true;  
    75.     ans += Solve(u, 0);  
    76.     int tcur = cur;  
    77.     for (int i = head[u]; i; i = edge[i].nxt) {  
    78.         int v = edge[i].to;  
    79.         if (vis[v]) continue;  
    80.         ans -= Solve(v, edge[i].w);  
    81.         Mn = inf;  
    82.         //cur = size[v];  
    83.         cur = size[u] > size[v] ? size[v] : tcur - size[u];  
    84.         Find_rt(v, u);  
    85.         Divide(root);  
    86.     }  
    87. }  
    88. int main() {  
    89.     read(n);  
    90.     int u, v, w;  
    91.     for (int i = 1; i < n; ++i) {  
    92.         read(u), read(v), read(w);  
    93.         insert(u, v, w), insert(v, u, w);  
    94.     }  
    95.     read(k);  
    96.     Mn = inf, cur = n;  
    97.     Find_rt(1, 0);  
    98.     Divide(root);  
    99.     printf("%d", ans);  
    100.     return 0;  
    101. }  
  • 相关阅读:
    python3之面向对象实例存家具
    python3之面向对象实例烤地瓜
    python3之批量修改文件名称
    python3处理大文件
    利用函数的递归计算数字的阶乘
    windows10安装.netframework3.5
    centos7-django(python3)环境搭建
    centos7-django(python3)环境搭建!
    Java线程池
    python设置编码
  • 原文地址:https://www.cnblogs.com/TY02/p/11267061.html
Copyright © 2011-2022 走看看