区间/树链染色问题
给定一个 \(n\) 个数的序列,\(m\) 个操作:每次对一个区间进行染色,其中染过的位置不用再染。
其中 \(n\le 10^6,m\le10^6\)
暴力模拟的时间复杂度是 \(O(nm)\) ,显然不能接受。考虑这个暴力慢在哪里?
暴力算法每次都要遍历一遍区间,由于染过的地方不能再染,所以暴力在已经染过的地方花费了大量时间,有没有办法排除这些 冗余 呢?
我们可以用并查集。
记 \(f_x\) 表示位置 \(x\) 自己及其后面第一个没有染色的地方,假设 \(x\) 这个位置被染色了,就将 \(f_x=f_{x+1}\)。在区间染色时,只需将循环这么写即可。
for(int i = l; i <= r; i = f[i]) {//在区间[l,r]染色
...
}
经过这样处理,每次跳到的位置都是没有染色的地方,于是不管 \(m\) 有多大,最多只会染色 \(n\le 10^6\) 次,于是时间复杂度就被降低到了 \(O(n)\).
原题:白雪皑皑
这类题目还有一种变式:
\(n\) 个数,每次对 \([l,r]\) 区间进行染色操作,可以覆盖掉已经染色的区域,问最后数列长什么样?
照题目来看,如果区间 \([l,r]\) 被染了蓝色,再在区间 \([l,r]\) 上染红色,最后这个区间应该是红色的,假如我们将这个过程倒过来:在 \([l,r]\) 上染红色后,再染蓝色就是无效的,和上一题 “染过的地方不能重复染” 有点像,推广到一般情况也没有问题。所以只需将整个过程倒过来就和上一题一模一样了。
上述是序列上的情况,接下来讨论树链上的情况。
其实是大同小异的,我们只需记录每个节点的父亲,再将深度大的向上跳,剩下的就和序列上的一模一样了。
例题:数列
经过一些简单的转化就变成对树链上的每一条边赋值(已经赋过的不用赋),由于是边比较特殊,设 \(f_x\) 表示从 \(x\) 向上跳,最远可以跳到的点,即中间的边全部经过赋值。每次对一个点赋值后,将 \(f_x=find(fa_x)\) 即可。
来总结一下这种问题的指导思想:
- 排除冗余:由于染过的地方没有必要再染,所以使用并查集跳过大大优化了时间复杂度。
- 与倍增的联系:一般来说,一个个跳导致超时,要么考虑倍增,要么考虑并查集,其中倍增可以处理静态的问题,这和 \(dsu\;on\;tree\) 有点像;但是并查集可以处理动态问题。