学习笔记:树分治

树分治用于解决有关路径的问题。
树分治分为点分治和边分治(其实还有一种叫“链分治”,是树的路径剖分思想的更高级的体现,一般链分治的题目都可以用路径剖分解决)。点分治就是每次找到重心,然后把重心去掉,对分成的每两棵树之间分别统计路径信息(以重心的每个相邻点为根,遍历整棵子树即可得到这个根到每个结点的统计信息),就可以知道包含这个重心的所有路径的信息,然后对于剩下的路径就是在子树里面进行同样的操作了,直到只剩一个点为止(注意这一个点所构成的路径有时也要处理一下)。边分治就是每次找到一条边,使得删掉这条边后分成的两棵子树大小尽可能平均,然后以删掉的边的两端点为根,分别统计根到两棵树中的每个结点的路径信息,最后合并算路径,即可得到包含这条边的所有路径的信息,剩下的路径在两棵树中递归处理。

点分治和边分治是可以通用的,不管树上的信息记录在结点上还是边上。这时一个很囧的问题就出现了:这两种分治到底哪个好?这也是本总结讨论的重点。
有关“哪个好”的问题显然要从三种复杂度上考虑。由于点分治和边分治的空间复杂度均为O(N),因此讨论空间复杂度木有意义。所以需要比较的就是时间复杂度和编程复杂度了。
先说时间复杂度。点分治每次找到重心后,需要将重心删掉然后分成很多棵子树,对每两棵子树之间都要统计一下。如果操作是可反的(比如计数问题:统计某种路径的条数),可以通过先将所有子树合在一起统计,再减去两端点处在相同子树内的路径,但是,如果操作不可反(比如找“最优路径”),就不能这样做了,只能枚举所有的子树对。显然,如果有S棵子树,则有O(S2)个子树对,最坏情况下(星形的树),S=N-1,此时一一枚举子树对必然会导致总时间复杂度升到O(N2)以上。当然这是有解决办法的,可以从小到大枚举子树,每处理完一棵子树就将其和前面的合并,此时算法效率取决于合并的方法。如果使用归并法(大小为A的和大小为B的合并次数为A+B),对于星形树的合并总次数仍然为O(N2),如果能保证大小为A的和大小为B的合并次数为min{A, B},则可以保证合并总次数在O(N)以内,从而可以保证总时间复杂度为O(NlogN)。边分治由于分成的永远是两棵子树,所以在处理子树之间的关系上能够保证O(N),问题是它并不能保证每次分成的两棵子树大小非常平均,在最坏情况下(星形的树),不管删哪条边分成的两棵子树大小都是一个1一个(N-1),这样就会使总的时间复杂度上升为O(N2)。

从以上的讨论中可以看出,点分治和边分治最怕的都是星形的树,也就是那种某个点的度数特别大的树。相关论文中提到一种办法通过加无用点的方式将其转化为二叉树,从而解决这个问题。这样一来,点分治和边分治的时间复杂度都可以保证了。接下来是编程复杂度的问题:点分治需要找重心(三次DFS或BFS),删掉重心(需要删点),且分成最多三棵树,需要处理三对关系,代码量肯定大一些,而边分治只需要算出子树大小后就可以找到最优边,并且分成的是两棵树,更重要的是,在有根树上删掉一条边后,分成的两棵树中有一棵已是有根树,另一棵可以通过扭根的方式将其转化为有根树,由于二叉树扭根是很方便的,所以就不需要每次重新建树,代码量较小。综合以上因素,边分治更好些。

另外,其实那种加无用点转化为二叉树的方式是有问题的,因为它改变了路径的意义,可能导致一条路径由于LCA是无用点而在统计时出现错误(明明是跨越了重心或最优边的路径被当成木有跨越的),但是……这个问题有一种很简单的解决办法,想知道是什么……AC了ALOEXT再告诉你……

相关题目:
e.g. (2013年候选队互测•a142857a) 树
边分治,每次找到所有点到根路径的XOR和,以及特殊点的个数,按照特殊点个数排序后有序插入Trie,查找即可。
代码:

  1 #include <iostream>
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <algorithm>
  6 using namespace std;
  7 #define re(i, n) for (int i=0; i<n; i++)
  8 #define re1(i, n) for (int i=1; i<=n; i++)
  9 #define re2(i, l, r) for (int i=l; i<r; i++)
 10 #define re3(i, l, r) for (int i=l; i<=r; i++)
 11 #define rre(i, n) for (int i=n-1; i>=0; i--)
 12 #define rre1(i, n) for (int i=n; i>0; i--)
 13 #define rre2(i, r, l) for (int i=r-1; i>=l; i--)
 14 #define rre3(i, r, l) for (int i=r; i>=l; i--)
 15 #define ll long long
 16 const int MAXN = 200010, MAXS = 31, INF = ~0U >> 2;
 17 struct edge {
 18     int a, b, pre, next;
 19 } E[MAXN << 1];
 20 struct sss {
 21     int v1, v2;
 22     bool operator< (sss s0) const {return v1 < s0.v1;}
 23 } S1[MAXN], S2[MAXN];
 24 int T[MAXN * MAXS][2];
 25 int n, m0, K, sum0, __No, N, A[MAXN], Q[MAXN], ls[MAXN], L[MAXN], R[MAXN], pr[MAXN], SZ[MAXN], V1[MAXN], V2[MAXN], res = -1;
 26 bool B[MAXN], vst[MAXN];
 27 void init_d()
 28 {
 29     re(i, n) E[i].pre = E[i].next = i; if (n & 1) m0 = n + 1; else m0 = n;
 30 }
 31 void add_edge(int a, int b)
 32 {
 33     E[m0].a = a; E[m0].b = b; E[m0].pre = E[a].pre; E[m0].next = a; E[a].pre = m0; E[E[m0].pre].next = m0++;
 34     E[m0].a = b; E[m0].b = a; E[m0].pre = E[b].pre; E[m0].next = b; E[b].pre = m0; E[E[m0].pre].next = m0++;
 35 }
 36 void init()
 37 {
 38     scanf("%d%d", &n, &K); init_d();
 39     int x, y;
 40     re(i, n) {scanf("%d", &x); B[i] = x;}
 41     re(i, n) scanf("%d", &A[i]);
 42     re2(i, 1, n) {scanf("%d%d", &x, &y); add_edge(--x, --y);}
 43 }
 44 int dfs0(int l, int r)
 45 {
 46     if (l > r) return -1; else if (l == r) return ls[l]; else {
 47         int n0; if (!l && r == sum0 - 1) n0 = __No; else n0 = n++;
 48         int mid = l + r >> 1, lch = dfs0(l, mid), rch = dfs0(mid + 1, r);
 49         L[n0] = lch; R[n0] = rch; pr[lch] = pr[rch] = n0; return n0;
 50     }
 51 }
 52 void prepare()
 53 {
 54     int x, y; Q[0] = 0; vst[0] = 1; pr[0] = -1;
 55     for (int front=0, rear=0; front<=rear; front++) {
 56         x = Q[front]; sum0 = 0;
 57         for (int p=E[x].next; p != x; p=E[p].next) {
 58             y = E[p].b;
 59             if (!vst[y]) {ls[sum0++] = y; vst[y] = 1; Q[++rear] = y;}
 60         }
 61         if (sum0 > 1) {__No = x; dfs0(0, sum0 - 1);} else if (sum0 == 1) {L[x] = ls[0]; pr[ls[0]] = x; R[x] = -1;} else L[x] = R[x] = -1;
 62     }
 63 }
 64 void solve(int No, int n0)
 65 {
 66     if (n0 == 1) {
 67         if (B[No] >= K && A[No] > res) res = A[No];
 68         return;
 69     }
 70     int x, _x, y, minv = INF, _n0 = n0 >> 1; Q[0] = No;
 71     for (int front=0, rear=0; front<=rear; front++) {
 72         x = Q[front];
 73         if ((y = L[x]) >= 0) Q[++rear] = L[x];
 74         if ((y = R[x]) >= 0) Q[++rear] = R[x];
 75     }
 76     rre(i, n0) {
 77         x = Q[i]; SZ[x] = 1;
 78         if (L[x] >= 0) SZ[x] += SZ[L[x]];
 79         if (R[x] >= 0) SZ[x] += SZ[R[x]];
 80         if (SZ[x] <= _n0 && _n0 - SZ[x] < minv || SZ[x] > _n0 && SZ[x] - _n0 < minv) {minv = SZ[x] <= _n0 ? _n0 - SZ[x] : SZ[x] - _n0; _x = x;}
 81     }
 82     x = pr[_x]; pr[_x] = -1; if (L[x] == _x) L[x] = -1; else R[x] = -1;
 83     int sum0 = 1; ls[0] = x; while ((y = pr[x]) != -1) {if (L[y] == x) L[y] = -1; else R[y] = -1; if (L[x] == -1) L[x] = y; else R[x] = y; x = ls[sum0++] = y;}
 84     pr[ls[0]] = -1; re2(i, 1, sum0) pr[ls[i]] = ls[i - 1];
 85     int len1 = 0, len2 = 0;
 86     Q[0] = _x; V1[_x] = B[_x]; V2[_x] = A[_x]; S1[len1].v1 = V1[_x]; S1[len1++].v2 = V2[_x];
 87     for (int front=0, rear=0; front<=rear; front++) {
 88         x = Q[front];
 89         if ((y = L[x]) >= 0) {Q[++rear] = y; V1[y] = V1[x] + B[y]; V2[y] = V2[x] ^ A[y]; S1[len1].v1 = V1[y]; S1[len1++].v2 = V2[y];}
 90         if ((y = R[x]) >= 0) {Q[++rear] = y; V1[y] = V1[x] + B[y]; V2[y] = V2[x] ^ A[y]; S1[len1].v1 = V1[y]; S1[len1++].v2 = V2[y];}
 91     }
 92     Q[0] = ls[0]; V1[ls[0]] = B[ls[0]]; V2[ls[0]] = A[ls[0]]; S2[len2].v1 = V1[ls[0]]; S2[len2++].v2 = V2[ls[0]];
 93     for (int front=0, rear=0; front<=rear; front++) {
 94         x = Q[front];
 95         if ((y = L[x]) >= 0) {Q[++rear] = y; V1[y] = V1[x] + B[y]; V2[y] = V2[x] ^ A[y]; S2[len2].v1 = V1[y]; S2[len2++].v2 = V2[y];}
 96         if ((y = R[x]) >= 0) {Q[++rear] = y; V1[y] = V1[x] + B[y]; V2[y] = V2[x] ^ A[y]; S2[len2].v1 = V1[y]; S2[len2++].v2 = V2[y];}
 97     }
 98     sort(S1, S1 + len1); sort(S2, S2 + len2); int _len2 = len2 - 1;
 99     N = 1; T[1][0] = T[1][1] = 0; int v0, _v;
100     re(i, len1) {
101         while (_len2 >= 0 && S1[i].v1 + S2[_len2].v1 >= K) {
102             v0 = S2[_len2--].v2; x = 1;
103             rre(j, MAXS) {
104                 y = (v0 & (1 << j)) > 0;
105                 if (!T[x][y]) {T[x][y] = ++N; T[N][0] = T[N][1] = 0;}
106                 x = T[x][y];
107             }
108         }
109         if (T[1][0] || T[1][1]) {
110             v0 = S1[i].v2; x = 1; _v = 0;
111             rre(j, MAXS) {
112                 y = (v0 & (1 << j)) > 0; _v <<= 1;
113                 if (T[x][!y]) {x = T[x][!y]; _v++;} else x = T[x][y];
114             }
115             if (_v > res) res = _v;
116         }
117     }
118     x = _x; y = ls[0];
119     solve(x, len1); solve(y, len2);
120 }
121 void pri()
122 {
123     printf("%d\n", res);
124 }
125 int main()
126 {
127     init();
128     prepare();
129     solve(0, n);
130     pri();
131     return 0;
132 }

时间: 2024-12-13 16:14:55

学习笔记:树分治的相关文章

学习笔记: cdq分治

今年的课程有很大一部分内容是cdq分治及其扩展(也就是二进制分组),拜读后觉得还是蛮有用的,这里小小地总结一下.(话说自己草稿箱里还有好多学习笔记的半成品呢,真是弱爆了.顺便感谢下fy与wxl向我介绍了那么好的东西) 推荐论文: 1 <从<Cash>谈一类分治算法的应用> 陈丹琦 2 <浅谈数据结构题的几个非经典解法>  许昊然 Q: cdq分治和普通的分治有什么区别? A: 在我们平常使用的分治中,每一个子问题只解决它本身(可以说是封闭的).而在cdq分治中,对于划分

基本数据结构学习笔记——树与二叉树

1.树的形式化定义: 树(Tree)是由一个或多个结点组成的有限集合T,其中有一个特定的称为根的结点:其余结点可分为m(m≥0)个互不相交的有限集T1,T2,T3 ,…,Tm,每一个集合本身又是一棵树,且称为根的子树. 2.有关树的基本术语: 1.结点(Node):树中的元素,包含数据项及若干指向其子树的分支. 2.结点的度(Degree):结点拥有的子树数. 3.结点的层次:从根结点开始算起,根为第一层. 4.叶子(Leaf):度为零的结点,也称端结点. 5.孩子(Child):结点子树的根称

Java-J2SE学习笔记-树状展现文件结构

1.利用java.io相关类树状展现文件结构 2.判定给定路径是否为dir,是则递归,每一递归一层缩进一次 3.代码 package Test; import java.io.File; public class TestHierarchical { public static void main(String[] args) { File file = new File("D:/Workspaces/eclipse/test"); tree(file, 0); } private s

[学习笔记]树链剖分

基本思想 树链剖分一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每条边属于且只属于一条链,然后再通过数据结构来维护每一条链. 一些定义 树链:树上的路径. 剖分:把路径分类为重链和轻链. 重儿子:u的子节点中siz[v]值最大的v. 轻儿子:u的其它子节点. 重边:点u与其重儿子的连边. 轻边:点u与其轻儿子的连边. 重链:由重边连成的路径. 轻链:轻边. 性质 如果(u,v)为轻边,则siz[v]$\times$2<siz[u]. 从根到某一点的路径上轻链.重链的个数都不大于l

&lt;学习笔记&gt; 树的直径 Bfs、Dfs

树的直径为树上最长的一条路径(不经过重复节点),也可以看做是树上最长路. 通常的求法: 1.两边Bfs或两边Dfs 2.树形dp(端点为根和仅经过根). emmm ..蒟蒻表示目前只会第一种QAQ. 从树中找出任意一点,求出与他距离最远的点s,再用同样的方法求出与s距离最远的点t,s-t即为树的直径. Bfs代码 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cma

《Hibernate学习笔记十一》:树状结构设计

<Hibernate学习笔记十一>:树状结构设计 这是马士兵老师讲解Hibernate的一个作业题,树状结构设计,这是一个比较典型的例子,因此有必要写篇博文记录下. 树状结构的设计,它是在同一个类中使用了多对一(ManyToOne)和一对多(OneToMany). 在完成这个题目我们应该按照如下的步骤进行: 1.先思考数据库的模型应该是什么样的?? 数据库中的模型应该如下:即存在id p_id 2.思考面向对象的模型,及如何来进行映射??? 根据数据库中表的特点,对象应该有id name;由于

线段树学习笔记

线段树学习笔记 20180112 http://www.cnblogs.com/wuyuanyuan/p/8277100.html 一定要明确需要维护的值(区间最大值.区间和--). 原文地址:https://www.cnblogs.com/wuyuanyuan/p/8278004.html

支配树学习笔记

支配树(dominator tree) 学习笔记 学习背景 本来本蒟蒻都不知道有一个东西叫支配树……pkuwc前查某位的水表看见它的大名,甚感恐慌啊.不过好在pkuwc5道题(嗯?)都是概率期望计数,也不知是好还是不好,我在这些方面也只是不好不差……扯远了. 考挂之后也没什么心思干别的,想起支配树这个东西,于是打算学一下. 技能介绍(雾) 支配树是什么?不如直接讲支配树的性质,从性质分析它的定义. 先大概讲一下它是来求什么的. 问题:我们有一个有向图(可以有环),定下了一个节点为起点s.现在我们

动态点分治学习笔记

学习动态点分治之前要先弄清楚点分治的原理,二者的应用范围的不同就在于动态的支持在线修改操作,而实现的不同就在于动态点分治要建点分树. OI中有很多树上统计问题,这类问题往往都有一个比较容易实现的暴力做法,而用高级数据结构维护信息有显得过于复杂,有没有一种"优美的暴力",能既保证思维的简单性,又有更高效的时间复杂度保证呢?这就是点分治的思想. 点分治的实现过程是:每次找到当前树的重心,然后以这个重心为根统计这个树的信息,然后对重心的每个孩子分别递归,同样用将重心作为根的方法统计子树的信息