区间/树链染色问题

给定一个 \(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\) 有点像;但是并查集可以处理动态问题
posted @ 2022-11-22 00:05  2017BeiJiang  阅读(43)  评论(0编辑  收藏  举报