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

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

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

  像是这道例题,多组询问是否存在长度为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;
  14. }

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

  接下来是分治和统计的过程。O(nlogn)实际上是算法框架的复杂度,实际复杂度会随统计手段而改变。

  针对这道题来说,我们通过一次暴力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)的统计,虽然复杂度没有变,但是接受不能。

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

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

(7.19)肝出了点分治的第二种写法。

  我们完全可以通过直接进行不重不漏的统计来避免容斥。处理u时,我们每次跑出点u的一个子树内的所有深度,把它们计入子树深度信息的同时,与之前得到的别的子树的信息组合,统计答案。注意这里要把点u本身计入这个child数组中,深度为0,这样就涵盖了路径结尾在u的情况。

代码:

  1. int chd[maxn], temp, tot;
  2. void dfs(int u, int pre, int d) {
  3. chd[++tot] = d;
  4. for (int i = head[u]; i; i = edge[i].nxt) {
  5. int v = edge[i].to;
  6. if (v == pre || vis[v])
  7. continue;
  8. dfs(v, u, d + edge[i].w);
  9. }
  10. }
  11. void solve(int u, int extra) {
  12. temp = tot;
  13. dfs(u, 0, extra);
  14. for (register int i = temp + 1; i <= tot; ++i)
  15. for (register int j = 1; j <= temp; ++j)
  16. ++ans[chd[i] + chd[j]];
  17. }
  18. void divide(int u) {
  19. vis[u] = true;
  20. //  memset(chd, 0, sizeof(chd));//直接覆盖原数组信息即可,不用拷贝也不用清空
  21. chd[1] = 0;
  22. tot = 1; //算上自己
  23. for (int i = head[u]; i; i = edge[i].nxt) {
  24. int v = edge[i].to;
  25. if (vis[v]) continue;
  26. solve(v, edge[i].w);
  27. }
  28. for (int i = head[u]; i; i = edge[i].nxt) {
  29. int v = edge[i].to;
  30. if (vis[v]) continue;
  31. Mn = inf, Size = size[v];
  32. find_rt(v, 0);
  33. divide(root);
  34. }
  35. }

  这段代码的实测效率比上一种写法快了一倍(开O2快10倍@w@)。下面放上完整的代码:

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. #define maxn 10010
  5. const int inf(0x3fffffff);
  6. using namespace std;
  7. template <typename T>
  8. void read(T &x) {
  9. x = 0;
  10. int f = 1;
  11. char ch = getchar();
  12. while (!isdigit(ch)) {
  13. if (ch == '-')
  14. f = -1;
  15. ch = getchar();
  16. }
  17. while (isdigit(ch)) {
  18. x = x * 10 + (ch ^ 48);
  19. ch = getchar();
  20. }
  21. x *= f;
  22. return;
  23. }
  24. int n, m;
  25. int head[maxn], top;
  26. struct E {
  27. int to, nxt, w;
  28. } edge[maxn << 1];
  29. inline void insert(int u, int v, int w) {
  30. edge[++top] = (E) {v, head[u], w};
  31. head[u] = top;
  32. }
  33. int Mn, root, Size, size[maxn], ans[10000010];
  34. bool vis[maxn];
  35. void find_rt(int u, int pre) {
  36. size[u] = 1;
  37. int Mxson = 0;
  38. for (int i = head[u]; i; i = edge[i].nxt) {
  39. int v = edge[i].to;
  40. if (vis[v] || v == pre)
  41. continue;
  42. find_rt(v, u);
  43. size[u] += size[v];
  44. if (size[v] > Mxson)
  45. Mxson = size[v];
  46. }
  47. Mxson = max(Mxson, Size - size[u]);
  48. if (Mxson < Mn)
  49. root = u, Mn = Mxson;
  50. }
  51. int chd[maxn], temp, tot;
  52. void dfs(int u, int pre, int d) {
  53. chd[++tot] = d;
  54. for (int i = head[u]; i; i = edge[i].nxt) {
  55. int v = edge[i].to;
  56. if (v == pre || vis[v])
  57. continue;
  58. dfs(v, u, d + edge[i].w);
  59. }
  60. }
  61. void solve(int u, int extra) {
  62. temp = tot;
  63. dfs(u, 0, extra);
  64. for (register int i = temp + 1; i <= tot; ++i)
  65. for (register int j = 1; j <= temp; ++j)
  66. ++ans[chd[i] + chd[j]];
  67. }
  68. void divide(int u) {
  69. vis[u] = true;
  70. //  memset(chd, 0, sizeof(chd));//直接覆盖原数组信息即可,不用拷贝也不用清空
  71. chd[1] = 0;
  72. tot = 1; //算上自己
  73. for (int i = head[u]; i; i = edge[i].nxt) {
  74. int v = edge[i].to;
  75. if (vis[v]) continue;
  76. solve(v, edge[i].w);
  77. }
  78. for (int i = head[u]; i; i = edge[i].nxt) {
  79. int v = edge[i].to;
  80. if (vis[v]) continue;
  81. Mn = inf, Size = size[v];
  82. find_rt(v, 0);
  83. divide(root);
  84. }
  85. }
  86. int main() {
  87. //  freopen("testdata.in.txt", "r", stdin);
  88. //  freopen("testdata.out", "w", stdout);
  89. read(n), read(m);
  90. int u, v, w, k;
  91. for (register int i = 1; i < n; ++i) {
  92. read(u), read(v), read(w);
  93. insert(u, v, w), insert(v, u, w);
  94. }
  95. Size = n, Mn = inf;
  96. find_rt(1, 0);
  97. divide(root);
  98. for (register int i = 1; i <= m; ++i) {
  99. read(k);
  100. puts(ans[k] ? "AYE" : "NAY");
  101. }
  102. return 0;
  103. }

由于通过枚举每一条可行路径来n^2进行统计,这种写法有很大的局限性。例如【P4178】Tree 这道题,直接枚举统计会爆炸,需要排序和双指针扫描的技巧来成段统计可行路径。在大多数情况下还是需要使用容斥去重的方法进行点分治。

【模版】【P3806】点分治的更多相关文章

  1. 【模板】P3806点分治1

    [模板]P3806 [模板]点分治1 很好的一道模板题,很无脑经典. 讲讲淀粉质吧,很营养,实际上,点分治是树上的分治算法.根据树的特性,树上两点的路径只有一下两种情况: 路径经过根\((*)\) 路 ...

  2. 洛谷 P3806 点分治模板

    题目:https://www.luogu.org/problemnew/show/P3806 就是点分治~ 每次暴力枚举询问即可,复杂度是 nmlogn: 注意 tmp[0]=1 ! 代码如下: #i ...

  3. 洛谷P3806 点分治

    点分治 第一次写点分治..感觉是一个神奇而又暴力的东西orz 点分治大概就是用来处理树上链的信息,把路径分成过点x和不过点x的两种,不过点x的路径可以变成过点x的子树中一点的路径,递归处理 #incl ...

  4. 洛谷P3806 点分治1 & POJ1741 Tree & CF161D Distance in Tree

    正解:点分治 解题报告: 传送门1! 传送门2! 传送门3! 点分治板子有点多,,,分开写题解的话就显得很空旷,不写又不太好毕竟初学还是要多写下题解便于理解 于是灵巧发挥压行选手习惯,开始压题解(bu ...

  5. 【Luogu】P3806点分治模板(点分治)

    题目链接 wc听不懂lca讲的高等数学专场(一个字都听不懂),然后就自学了点分治. 点分治就是我先处理完跟根有关的东西,然后把根标记掉,把原树拆成若干个联通块,然后分别对每个联通块(每个小树)搞一模一 ...

  6. Luogu P3806 点分治模板1

    题意: 给定一棵有n个点的树询问树上距离为k的点对是否存在. 分析: 这个题的询问和点数都不多(但是显然暴力是不太好过的,即使有人暴力过了) 这题应该怎么用点分治呢.显然,一个模板题,我们直接用套路, ...

  7. 模板·点分治(luogu P3806)

    [模板]洛谷·点分治 1.求树的重心 树的重心:若A点的子树中最大的子树的size[] 最小时,A为该树的中心 步骤: 所需变量:siz[x] 表示 x 的子树大小(含自己),msz[x] 表示 其子 ...

  8. [luogu P3806] 【模板】点分治1

    [luogu P3806] [模板]点分治1 题目背景 感谢hzwer的点分治互测. 题目描述 给定一棵有n个点的树 询问树上距离为k的点对是否存在. 输入输出格式 输入格式: n,m 接下来n-1条 ...

  9. 洛谷 P3806 【模板】点分治1

    P3806 [模板]点分治1 题目背景 感谢hzwer的点分治互测. 题目描述 给定一棵有n个点的树 询问树上距离为k的点对是否存在. 输入输出格式 输入格式: n,m 接下来n-1条边a,b,c描述 ...

随机推荐

  1. 湖湘杯2020_ReMe

    查壳后发现是由Python2.7环境下编译得到的exe可执行文件 由此想到可将exe转为pyc文件再反编译成py文件 且该方法只适用于py2 无混淆 因为py3的字节码结构有些许变化 step1: 在 ...

  2. django基础回顾

    1,web项目工作流程 1.1 了解web程序工作流程 1.2 django生命周期2,django介绍 目的:了解Django框架的作用和特点 作用: 简便.快速的开发数据库驱动的网站 Django ...

  3. 考场(NOIP/ICPC)沙雕错误锦集(大赛前必看,救命提分良药)

    记住,无论什么测试,一定要先打三题暴力(至少不会被屠得太惨) 2018.10.4 1.记得算内存.(OI一年一场空,没算内存见祖宗) 2018.10.6 1.在二分许多个字符串时(二分长度),要以长度 ...

  4. [LuoguP2161[ [SHOI2009]会场预约 (splay)

    题面 传送门:https://www.luogu.org/problemnew/show/P2161 Solution splay 的确有线段树/树状数组的做法,但我做的时候脑残没想到 我们可以考虑写 ...

  5. 使用 Iceberg on Kubernetes 打造新一代云原生数据湖

    背景 大数据发展至今,按照 Google 2003年发布的<The Google File System>第一篇论文算起,已走过17个年头.可惜的是 Google 当时并没有开源其技术,& ...

  6. Netty源码解析 -- ChannelOutboundBuffer实现与Flush过程

    前面文章说了,ChannelHandlerContext#write只是将数据缓存到ChannelOutboundBuffer,等到ChannelHandlerContext#flush时,再将Cha ...

  7. 【SpringBoot】15. Spring Boot核心注解

    Spring Boot核心注解 1 @SpringBootApplication 代表是Spring Boot启动的类 2 @SpringBootConfiguration 通过bean对象来获取配置 ...

  8. leetcode143zigzag-conversion

    题目描述 字符串"PAYPALISHIRING"写成3行的Z字形的样式如下: P A H N↵A P L S I I G↵Y I R 按行读这个Z字形图案应该是 "PAH ...

  9. 管理机--Jumpserver由docker搭建

    一.环境准备 使用Centos7.0及以上版本,(网要好哦) 二.安装docker 1,下载,安装,启动 docker yum -y install docker         #安装docker ...

  10. 大数据分析中数据治理的重要性,从一个BI项目的失败来分析

    很多企业在做BI项目时,一开始的目标都是想通过梳理管理逻辑,帮助企业搭建可视化管理模型与深化管理的精细度,及时发现企业经营管理中的问题. 但在项目实施和验收时,BI却变成了报表开发项目,而报表的需求往 ...