树分治(三)——点分治题目选讲

点分治题目选讲

CF711C Bear and Tree Jumps Present 5

尽管这题可以有 \(O(nk^2)\) 的 dp 做法,但是仅限于边权为 \(1\) 的情况。

而点分治可以处理边权任意的情形!考虑当前根为 \(\text{rt}\) 的时候怎么算。

对每个 \(u\) 处理出来 \(\text{rt}\to u\) 路径上的边权和,记作 \(d_u\),那么要求的就是:

\[\sum_{b_u\neq b_v}\left\lceil\dfrac{d_u+d_v}{k}\right\rceil \]

其中 \(b_u\) 表示 \(u\) 是从哪个子树过来的。我们要能快速算出这个东西就行了。

注意到 \(k\) 很小,考虑将 \(d_u\) 按照 \(\bmod k\) 的余数分类。

由于 \(d_u+d_v=k\lfloor d_u/k\rfloor+(d_u\bmod k)+k\lfloor d_v/k\rfloor +(d_v\bmod k)\),故

\[\left\lceil\dfrac{d_u+d_v}{k}\right\rceil=\lfloor d_u/k\rfloor+\lfloor d_v/k\rfloor+\left\lceil\dfrac{(d_u\bmod k) +(d_v\bmod k)}{k}\right\rceil \]

前一项可以直接算,考虑后一项怎么算。

\(f_i\) 为当前子树内满足 \(d_u\bmod k=i\)\(u\) 的个数,\(g_i\) 为前面子树内 \(\bmod k=i\) 的个数,那么后一项的和就是

\[\sum_{i=0}^{k-1}\sum_{j=0}^{k-1}g_if_j\left\lceil\dfrac{i+j}{k}\right\rceil=\sum_{i=0}^{k-1}g_i\left(\sum_{j=1-i}^{k-i}f_j+2\sum_{j=k-i+1}^{k-1}f_j\right) \]

维护 \(f,g\) 以及 \(f\) 的前缀和即可。\(O(nk\log n)\)AC Code

但说实话这题其实挺没意思的,,实际上我们可以利用长链剖分在 \(O(n)\) 时间内对每个 \(x\) 预处理出来长度为 \(x\) 的路径数 \(c_x\),然后答案就是 \(\sum c_i\lfloor i/k\rfloor\)

点分治也可以算这个 \(c\) 数组,只不过麻烦一点,我目前只会 \(O(n\log ^2n)\) 的点分治+NTT >_<

CF833D Red-Black Cobweb Future 7.0

考虑点分治,设当前根为 \(r\),对于一个点 \(u\) 我们算出来 \(f_u,g_u\) 表示 \(r\to u\) 路径上黑边白边的个数。

那么一条路径合法当且仅当

\[\dfrac{1}{2}\le \dfrac{f_u+f_v}{g_u+g_v}\le 2 \]

变形得到 \(2f_u-g_u\le g_v-2f_v,f_u-2g_u\le 2g_v-f_v\)。貌似是一个二维数点的形式,复杂度 \(O(n\log ^3n)\)

其实可以容斥:我们算出来比例 \(<1/2\) 与比例 \(>2\) 的路径乘积,然后容斥一下即可。这样就变成一维数点了,复杂度 \(O(n\log ^2n)\)AC Code

CF715C Digit Tree Present 7.0

考虑点分治。对于当前根 \(\text{rt}\),对每个 \(u\) 预处理出来 \(\text{rt}\to u\) 路径上边权写下来之后 \(\bmod M\) 的值 \(f_u\)\(u\to \text{rt}\) 路径上边权写下来后 \(\bmod M\) 的值 \(g_u\) 以及深度 \(d_u\)。其中 \(d_\text{rt}=0\)

那么两条路径拼一起的时候就是 \(u\to \text{rt}\to v\),也就是要求 \(g_u+f_v\times 10^{d_u}\equiv 0(\bmod M)\)

也就是 \(f_v\equiv -g_u\times 10^{-d_u}(\bmod M)\)。由于 \(\gcd(M,10)=1\)\(10^{-1}\)\(\bmod M\) 意义下确实存在。

那么就可以维护一个 map 然后直接算啦

时间复杂度 \(O(n\log n\log M)\)\(\log M\) 来自 mapAC Code

ps:这题 \(M\) 不一定是质数,求逆元不能直接算 \(10^{M-2}\bmod M\) !!!!我因为这个调了四天!!

ABC248G GCD cost on the tree Future 7.0

考虑点分治,对于当前根 \(\text{root}\),设 \(g_u\)\(\text{root}\to u\) 路径上点权 \(\gcd\)\(f_u\)\(u\) 的深度。

依次处理 \(\text{root}\) 的每一棵子树,并维护一个集合 \(S\)

  • 对于当前子树内的每个点 \(u\),算出 \(f_u\)\(g_u\),并将 \(\sum_{v\in S}(f_u+f_v)\gcd(g_u,g_v)\) 累计进答案。
  • 将每个点 \(u\) 插入进集合内。

现在只需要考虑如何维护集合 \(S\)

简化一下问题,我们需要维护一个集合 \(S\) ,支持加入一个数,以及对于给定的数 \(y\),查询

\[\sum_{x\in S}\gcd(x,y) \]

注意到

\[\sum_{x\in S}\gcd(x,y)=\sum_{x\in S}\sum_{d|\gcd(x,y)}\varphi(d)=\sum_{d|y }\varphi(d)\sum_{x\in S}[d|x] \]

预处理 \(\varphi\),并维护每个数在 \(S\) 中的倍数个数即可。

那么这道题也就相当于带了个权,把贡献拆成两部分就行了。

更具体地,我们要查询的东西相当于

\[\sum_{x\in S}\gcd(x,y)\cdot (f_x+f_y)=\sum_{x\in S}\sum_{d|x,d|y}\varphi(d)(f_x+f_y)=\sum_{d|y}\varphi(d)\sum_{x\in S}[d|x](f_x+f_y)\\=\sum_{d|y}\varphi(d)\left(\sum_{x\in S}[d|x]f_x+f_y\sum_{x\in S}[d|x]\right) \]

前面一部分就是带了个权,后面一部分就是不带权的情况乘上 \(f_y\)。本质上没有什么变化。

时间复杂度:我们每次加入与删除的复杂度都是 \(O(d(y))\),其中 \(d(x)\) 表示 \(x\) 的约数个数。

随便写个程序验证一下就可以发现 \(\max_{i\in[1,10^5]}d(i)=128\),所以复杂度并不会太高。

总的复杂度为 \(O(nd(V)\log n+V\log V)\),其中 \(V=10^5\) 为值域。\(V\log V\) 来自预处理约数集合。

由于 \(d(V)\) 在大多数情况下只有 \(O(\log V)\),根本卡不满 \(128\),所以常数很小,可以看作 \(O(n\log ^2n)\)AC Code

Luogu3714 BJOI2017 树的难题 Present 7.0

点分治,设当前分治中心为 \(r\),那么对于两条路径拼起来的时候,有两种情况:

  • 两条路径最上面的颜色不同,那么直接把路径边权加起来就好了。
  • 两条路径最上面的边的颜色相同,相当于把路径边权加起来再减掉一个这个颜色的权值。

分别用线段树维护区间 max 单点修改就行了,复杂度 \(O(n\log ^2n)\)AC Code

CF150E Freezing with Style Future 8.0

给定一颗带边权的树,求一条边数在 \([L,R]\) 之间的路径,并使得路径上边权的中位数最大。输出一条可行路径的两个端点。

点数不超过 \(10^5\),边权 \(\le 10^9\)。这里,一个长为 \(k\) 的序列 \(a_0,a_1,\cdots,a_{k-1}\) 的中位数为 \(a_{\lfloor k/2\rfloor}\),不论 \(k\) 的奇偶。

首先二分。二分一个值 \(m\),那么一个序列中的中位数 \(\ge m\),就相当于这个序列中至少一半的数都 \(\ge m\)

因此把 \(\ge m\) 的边权设为 \(1\)\(<m\) 的边权设为 \(-1\),求出此时树上边权和最大的路径,判断一下是否 \(\ge 0\) 即可。

现在的问题就是:求出树上边数在 \([L,R]\) 之间的路径,使得路径上边权和最大。

点分治。设当前处理到了根 \(\text{root}\) 的第 \(i\) 个子树。我们定义:

  • \(p_x\):在前 \(i-1\) 个子树内所有与 \(\text{root}\) 距离为 \(x\) 的节点中,路径 \(\text{root}\to x\) 上边权和的最大值。
  • \(q_x\):当前子树内所有与 \(\text{root}\) 距离为 \(x\) 的节点中,路径 \(\text{root}\to x\) 上边权和的最大值。

那么写一下我们要求出来的东西就是:

\[\max\{p_x+q_y|x+y\in[L,R]\} \]

枚举 \(y\),我们要求的就是 \(p\)\([L-y,R-y]\) 上的最小值。看上去可以直接开一个线段树求区间最小值。

分析一波复杂度:点分治每一层一个 \(O(n\log n)\),一共是 \(O(n\log ^2n)\)。再乘上二分就得到了一个很 naive 的 \(O(n\log ^2n\log V)\) 的 3log 做法,而且常数巨大。。。

显然过不去......考虑优化一下。

注意到我们枚举 \(y\) 的时候如果按照递增顺序,那么 \([L-y,R-y]\) 这个区间就在不断地从右至左滑动。

因此可以考虑用一个单调队列来维护滑动窗口最小值,这样点分治每一层就是 \(O(n)\) 了......?

然而并不是。我们处理到第 \(i\) 棵子树时,设前 \(i-1\) 棵子树内最深子节点深度为 \(\text{Maxdep}\),那么处理这一棵子树就需要 \(O(\text{Maxdep})\) 的时间。

举个例子,如果第一个子树深度达到 \(O(n)\),剩下的都是 \(1\),点分治一层的时间复杂度就成了 \(O(n^2)\)。看上去这个方法不行了。

其实解决方法很简单:还是以刚才那个例子来说,如果我们先处理那些深度浅的子树,那么显然就不会有这种问题。

因此可以考虑把所有子树按最大深度排序,然后从小到大依次处理。这里子树的深度指子树内最深节点的深度。

这样一来,由于我们是按深度从小到大处理的,设当前子树深度为 \(\text{dep}\),那么前 \(i-1\) 个子树中的 \(\text{Maxdep}\) 必然不会超过 \(\text{dep}\)

关于排序的复杂度,注意到我们排序的点数实际上是根节点的子节点个数,这个东西的和是 \(O(n)\) 的,所以排序的总复杂度是 \(O(n\log n)\)

因此,总的复杂度就是 \(O\big(\sum \text{dep}\big)=O(n)\) 了。总的时间复杂度为 \(O(n\log n\log V)\)

注意到每次点分治的时候树的结构不变,因此可以提前预处理出来我们对这些点的访问顺序,然后每次二分的时候直接 for 循环,就省掉了递归的常数和找重心的时间。

代码:写挂了,留个坑

UPD:用长链剖分过了 <>

LuoguP4292 WC2010 重建计划 Future 8.0

给定一棵带边权的树,求一条边数在 \([L,U]\) 之间的路径,并使得路径上边权的平均数最大。

点数 \(10^5\),边权 \(10^6\)

和上一题基本一样。。

二分答案 \(m\),将所有边权都减去 \(m\),就相当于找一条边权和非负的路径。直接做就行了。

Luogu2664 树上游戏 Future 8.1

考虑点分治,设当前根为 \(r\)

对于一个点 \(u\),考虑把颜色数之和拆成两部分,一部分是在 \(r\to u\) 路径上出现过的,一部分是没有出现过的。

对于在 \(r\to u\) 路径上出现过的颜色,其贡献即为 所有点的个数 减去 \(u\) 所在子树的节点个数;对于没有在 \(r\to u\) 路径上出现过的颜色 \(c\),我们维护 \(f_i\) 表示颜色 \(i\) 出现在了多少条路径上,贡献就是 \(f_c\)

这些东西都可以在 dfs 中顺便维护掉,于是这题就做完了。复杂度 \(O(n\log n)\)AC Code

posted @ 2022-07-16 15:50  云浅知处  阅读(175)  评论(0)    收藏  举报