树分治
点分治
应用:
- 树上所有路径统计
- 树上每个节点做根,信息统计。
注意:需要在求出重心后重新统计子树大小,别写假了。
P3085 [USACO13OPEN] Yin and Yang G
将两种颜色边权分别设置为 \(-1\) 和 \(1\),转化为三段路径和为 \(0\)。如果统计中间的那个点和另一个端点的话需要用到乘法原理,有些麻烦而且无法排除同一端点不同中点的情况。可以对于端点分类:祖先有可以做中点的点和祖先没有可以做中点的点,然后分来统计即可。
P4886 快递员
思路新奇的题目,如果枚举所有点做根,然后统计是 \(O(n^2)\) 的。可是我们发现其实可以缩减计算量,假设当前以 \(c\) 为根,存在一个组 \((u_i,v_i)\) 使得两者是最长路径且经过 \(c\),或者存在至少两组 \((u_i,v_i)\) 使得 \(u_i\) 和 \(v_i\) 在同一子树内,且两组点对不同子树。这样子就无法缩减答案了,否则进入那颗包含最大 \((u_i,v_i)\) 的子树。借鉴点分治思想,每次选取重心,复杂度是 \(O(n\log n)\)。
P2664 树上游戏
法一:点分治
每次算出其他子树内包含颜色 \(i\) 的节点数 \(cnt_i\),然后计算其他子树内总节点数 \(tot\)。每到一个点先累加 \(\sum cnt_i\),然后对于 \(u \to rt\) 路径上的每种颜色累加 \(tot-cnt_c\)。同时传递一下标记。
法二:这里其实有线性做法。
从每个颜色角度考虑,包含某个颜色有点难处理,可以改为不含某颜色,只需要把那个颜色的点都去掉,形成多个连通块,给连通块内每一个点加上快的大小,代表从该点出发不包含某颜色的数量。答案也就是总数减去不包含的数量。每个颜色单独处理显然重复处理信息了。因为这不是图是树,而树上每个点就是割点,连通块的产生应该是很容易的,只需要某个点的连或断所以我们应该是可以把所有颜色一起处理的。
假设 dfs 到了点 \(u\) 颜色为 \(c\),那么便会产生一个 \(c\) 的连通块,这连通块到哪里终止呢,就是到另一个 \(c\) 的子树,于是我们只需要给除去 \(c\) 子树的部分加上 \(sz-\sum sz'\) 就行了,其中 \(sz\) 代表 \(u\) 子树的大小, \(sz'\) 代表有边为 \(c\) 的子树的大小。这个过程可以通过树上差分实现。别忘记对于 \(1\) 点单独处理一下。
小技巧:如何找到子树中所有颜色为 \(c\) 的点:利用 dfs 序加 vector 即可。
P4183 [USACO18JAN] Cow at Large P
设点的深度和到叶子节点的距离分别是 \(dep_i\) 和 \(g_i\),一个点可以封锁的条件就是 \(dep_i \ge g_i\),可是我们要求封锁点尽量少,也就希望这个点尽可能往上,等价于儿子满足条件但父亲不满足这个条件。如果单纯是条件一的话可以用换根 dp 来求,可是每个点的父亲无法再换根,这就很难做。
观察一下我们会发现这里其实是一个子树产生 "1" 的贡献,这里有一个公式就是子树内 \(\sum(2-deg_i)=1\),于是我们只需要对于每个满足要求的点 \(u\) 产生 \(2-deg_u\) 的贡献即可。点分治统计点对 \((u,v)\) 的贡献。于是对于任意根节点 \(r\),求 \(\sum\limits_{d_{r,u\ge g_r(u)}}(2-deg_u)\)。
注意一下,对于那个度数求和公式的使用,首先是无向树,其次注意是“子树”,所以子树的根向上还有一个度也要算。
CF833D Red-Black Cobweb
对于边占比的信息不太好直接合并出来,于是我们考虑维护 \(a-kb\) 形式的信息使得合并之后可以快速查看是否满足比例约束。
具体来说我们设黑边个数为 \(a\),白边个数为 \(b\),那么我们维护四个信息 \((A,B,C,D)=(a-2b,2b-a,b-2a,2a-b)\)。
于是 \((u,v)\) 点对可以产生贡献必须满足,\(A_u\le B_v\) 且 \(C_u\le D_v\)。这是一个二维数点的形式。
直接排序 \(+\) 树状数组统计即可。如果去除同一子树内贡献呢,我们对于当前分治重心下同一子树的信息单独跑一边上述计算减去贡献即可。但是这么做感觉有点问题,就同一点对会产生重复贡献,由于是乘法需要开根号,这就需要取模意义下的开根号也就是二次剩余有点麻烦。
为了防止点对产生重复贡献,我们可以赋一个顺序,也就是子树顺序,然后直接 CDQ 即可。
时间复杂度 \(O(n\log^3 n)\)。
边分治
点分治产生多颗子树,但是有的时候信息难以合并,这个时候就要用到边分治这样只会产生两部分,方便操作。防止超时应该用多叉树转二叉树
BZOJ 2870. 最长道路tree
直接对于边 \(i\) 进行边分治即可,然后双指针扫描。
动态点分治
就是多次询问的要用到点分治结构的东西。
我们对于点分治的每层重心之间连边得到的树就是点分树。
这样子树高是 \(O(\log n)\) 级别的,我们可以暴力跳点分树上的父亲来求解答案,注意细节需要去掉父子之间重复的信息。
P6329 【模板】点分树 | 震波
模板题。直接对于每个点 \(u\) 维护自点分树子树内距离自己为 \(k\) 的点权和,记为 \(C_{0,u,k}\)。查询就是不断跳父亲累加答案。思考一下为什么是这样子,因为这本质是一个路径统计问题,我们需要统计 \(u-x\) 距离 \(\le k\) 的 \(\sum a_x\),路径统计可以类似点分治的方法解决,但是多次询问复杂度过高。在我们保存了各层分治中心之后,可以发现所有 \(u-x\) 路径必定可以拆分为 \(u-anc-x\),其中 \(anc\) 为 \(u\) 在点分树上的祖先,根据点分治统计的完全性,可以知道这个方法是正确的,可以这么拆。
下面还要解决两个问题,一个是动态修改,很好办,直接把 \(C\) 替换为树状数组即可。还有一个就是 \(u,fa_u\) 之间的统计重复。我们可以设 \(C_{1,u,k}\) 表示在 \(u\) 子树内和 \(fa_u\) 距离为 \(k\) 的 \(\sum a_x\)。每次更新点权的时候就直接暴力跳父亲,更新一路上的 \(C_0\) 和 \(C_1\)。每次查询的时候就直接一边加 \(C_0\) 的贡献,一边减去 \(C_1\) 的贡献。
我们使用 vector 来储存 \(C\),对于 \(C_{0,u}\) 直接开 \(C\) 的最大深度大小,对于 \(C_{1,u}\) 要开 \(f_u\) 的最大深度大小。
P3676 小清新数据结构题
树链剖分解法
我们可以先动态维护根为 \(1\) 的时候的答案。有一个很简单的技巧就是把修改变为增加,对于一个点的修改影响的是所有祖先的子树和,于是我们只需要支持快速链加,全局求和即可。\(\sum (s_i+x)^2=s_i^2+2\times s_i\times x+x^2=\sum s_i^2+2x\sum s_i+len\times x^2\)。树链剖分维护即可。
然后考虑换根到 \(u\),不妨设路径为 \(1=u_0-u_1-...-u_k=u\)。跟为 \(1\) 和 \(u\) 的时候子树和分为 \(a_i\) 和 \(b_i\)。
答案为 \(ans_u=ans_1-\sum\limits_{i=0}^ka_i^2+\sum\limits_{i=0}^kb_i^2\)。
又因为 \(a_{i+1}+b_i=a_0=b_k\),可以得到
拆开维护一波即可。
点分树
考虑将根从 \(1\) 一步一步移动到 \(u\),发现每次移动后都只有两个值会发生变化,也就是 \(s_{rt}\to sum-s_i\) 还有 \(s_i \to s_{rt}\)。
于是我们可以发现 \(\sum(sum-s_i)\times s_i\) 为定值。
又因为 \(\sum s_i^2\) 正是我们需要求解的值,所以我们只需要维护 \(sum \times \sum s_i\) 即可。
\(sum\) 很容易维护,现在的问题就在 \(\sum s_i=\sum a_i\times (dis_{i,x}+1)\) 上面,这是动态点分治的模板。
在修改点权的情况下,\(\sum(sum-s_i)s_i\) 是会变化的,我们需要快速求值。考虑一下这个式子的意义,其实就是沿着 \(fa_i \to i\) 这条边把整个树划分成两个部分,然后两个部分的点对乘积和。再对于所有划分求和。
单点修改,变化量就是 \((y-a_x)\times\sum\limits_{i=1}^na_j\times dis(x,j)\),又因为以 \(x\) 为根的树中,\(\sum s_i=\sum\limits_{i=1}^na_i\times (dis(i,x)+1)\)。所以直接维护即可。