Preface
Debug了半个晚上,有必要总结一下。
今天打了两三个线段树合并的题,深深感到数据结构对手残党的恶意。动辄上百行的代码,一定要非常熟练才有可能在真正比赛的时候打出来。
题意
有一张无向图, n n n个点, m m m条边,常数 L L L。有点权(记为 c [ i ] c[i] c[i])、边权( d [ u , v ] d[u, v] d[u,v])。求满足 u < v , ∣ c [ u ] − c [ v ] ∣ ≥ L u<v,|c[u]-c[v]|\geq L u<v,∣c[u]−c[v]∣≥L的点对之间的路径上最大边权的最小值。
讲的还没有原题清楚。。。
有一张 n 个点,m 条边的带权无向图。第 i 个点的颜色为 ci。d(s, t)表示从点 s 到点 t 的权值最小的路径的权值,一条路径的权值定义为路径上权值最大的边的权值。求所有满足 u < v, |cu − cv| ≥ L 的点对 (u, v) 的 d(u, v) 之和。
思路
线段树合并
首先很明显的,对于边权最大的最小值,可以用 K r u s k a l Kruskal Kruskal的贪心思路解决。
但是与模板不同的是,这题在合并 f a [ u ] = v fa[u]=v fa[u]=v时,还需统计答案。那么又很容易想到用一颗权值线段树维护每个已合并的块的所有点的颜色,在合并两个块时枚举其中一个块的所有点,在另一个块中查找颜色与它符合要求的点,更新答案。
但是又有两个问题:
- 对每一个节点都开一颗线段树,明显空间不够。
- 合并的复杂度好像有点不对劲。
对于Question1,可以用动态开点线段树,甚至不用离散化,总空间复杂度大概在
O
(
n
l
o
g
(
1
e
9
)
)
O(nlog(1e9))
O(nlog(1e9))左右。但是我写的线段树合并出了一点问题,加了离散,再回收节点,才脱离RE。实际上是没有必要的?看高分程序里面有好多没有回收节点的呢
对于Question2,有一种玄学算法叫启发式合并。因为人类的智慧,这样合并只需要
O
(
n
l
o
g
n
∗
(
t
i
m
e
  
p
e
r
  
m
e
r
g
e
)
)
O(nlogn*(time\;per\;merge))
O(nlogn∗(timepermerge)),那么在此题的线段树合并上(这题每个原始线段树只有一条链)复杂度就是
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)。
再记录几个傻逼问题,如果有人也看了,把这些当做前车之鉴,不要再翻车了:
- 特判L=0时忘记写启发式合并的交换,T了好几发。
- 没有预处理c[i]+L-1和c[i]-L+1的离散值,复杂度可能整整多了一个log,然而我一点意识都没有。
- 空间问题,预估空间不够的情况下可以写个TrashBin回收一下。
代码就不放了,调到最后濒临崩溃,丑的不能看。
Kruskal重构树+二维数点
题解思路,其实就是把线段树合并变成数点。
先建出 Kruskal 重构树,每条边的贡献次数为它连接的两个子树之间的颜色之差大于等于 L 的点对数,可以发现 ∑ min(size(lef tchildi), size(rightchildi)) = O(n log n)。
对于每条边我们枚举 size 较小的那棵子树内的点,算出在另一棵子树中能与它组成点对
的点的个数。
这个问题实际上就是询问在 dfs 序的一段区间上并且颜色不在一段区间内的点数,二维数点问题可以离线树状数组完成。
总的时间复杂度为 O(m log m + n log2n)。
其实我不会