我们给每个连通块图上一种颜色。不同的连通块涂不同的颜色。
首先,我们定义\(f_r\)表示:使\([l,r]\)包括\([1,r]\)里所有颜色的最大的\(l\)。
然后我维护一个变量\(pos\),表示从\(pos\)到\(n\)的这些点\(i\)(\(\forall i\in[pos,n]\)),\([1,i]\)中包含了当前所有的颜色。容易发现这个条件就等价于\([1,pos]\)中包含了所有颜色。对每次操作显然\(pos\)是单调减的,所以就很好维护。在并查集时,令每个连通块的根,也就是fa[x]==x
的那个节点,是这个连通块里的最小位置。即在合并时:fa[max(x,y)]=min(x,y)
。这样我们就可以这么维护\(pos\):
//初始化:
pos=n;
//每次询问时:
while(pos>1&&get_father(pos)!=pos)pos--;
有了\(pos\)之后,一次询问的答案就是对\([pos,n]\)这段的每个\(i\),求\(i-f_i+1\)的最小值。这就是\(f_i\)的作用。
我们定于\(\text{next}_i\)表示\(i\)后面第一个和\(i\)同色的位置。则容易发现\(\displaystyle f_i=\min_{\text{next}_j>i}\{j\}\)。其含义是:对于每一种颜色,你取它在\(i\)前面最后一次出现的位置,然后对这些位置取\(\min\),得到的就是使得\([l,i]\)包含所有\(i\)前面的颜色的最大的\(l\)了。如果已知了\(\text{next}\)数组,我们用线段树维护\(\text{next}\)的区间最大值,则某个点\(i\)的\(f_i\)可以通过线段树上二分,在\(O(\log n)\)的时间内求出。
线段树上二分程序片段:
int query(int p,int l,int r,int pos){
if(l==r){
assert(mx[p]>pos);
return l;
}
int mid=(l+r)>>1;
if(mx[p<<1]>pos)return query(p<<1,l,mid,pos);
else return query(p<<1|1,mid+1,r,pos);
}
回到问题,首先我们肯定要启发式合并,这样我们就有机会暴力更改集合里的每个点,便于我们维护\(f\)。
对于一个点\(u\),如果:
\(u\)的颜色变了;
或\(u\)左边新插入了一个颜色和\(u\)不一样的点;
或\(u\)右边新插入了一个颜色和\(u\)不一样的点;
那么原本一些位置的\(f\)值就会变化。具体哪些位置会变呢,我们发现只有\(f_i=u\)的这些\(i\)会变。又因为\(f\)是单调递增的,所以\(f_i=u\)的\(i\)一定在一段连续的区间。
证明:只有原本\(f_i=u\)的这些\(i\)的\(f\)值会变。
考虑\(f_i<u\)时,这样的\(f_i\)瓶颈不在\(u\),也就是说它在取\(\min\)时会取到一个比\(u\)还小的位置。所以这样的\(f_i\)显然不会变。
\(f_i>u\)时,说明\(u\)后面还有一个和\(u\)同色的点在\(i\)之前,所以\(u\)就和\(i\)无关了。
现在我们考虑求出\(f_i=u\)的\(i\)的区间。我们用另一棵线段树,维护\(f\)的区间最大值、区间最小值。这样就可以线段树上二分出这个区间。
考虑这段区间\([l,r]\),它在我们进行操作前\(f\)值是相等的,操作后它会分裂成若干段区间。我也不知道具体会分裂成几段。
但是!
最多只有\(2\)段会和前后的合并起来。(这里的合并是指变成\(f\)值相同的连续段,这只是我们证明复杂度用的,在代码里并不需要真的合并)。而整个序列最多只有\(n\)段\(f\)值,每次最多只会减小\(2\)段,因此增加的段数总量不超过\(n+2m\),是\(O(n)\)级别的!
这样我们就可以一段一段暴力跳了。
最后的问题是对于一个\(l\),如何跳到最靠后的一个和它\(f\)值相等的\(r\)(这里指修改后的\(f\)值)。
首先根据最开始的讨论,我们可以求出\(l\)在修改后的\(f\)值,我们记为\(pre\)。则\(l\)之后的第一个和\(l\)的\(f\)值不同的地方在\(\text{next}_{pre}\)。
证明:
对于每个\(i\in[l,\text{next}_{pre})\),\(f_i\)首先不可能小于\(f_l\),因为这样不符合\(\displaystyle pre=\min_{\text{next}_j>l}\{j\}\)的性质。
然后\(f_i\)也不可能\(>pre\),因为\(i<\text{next}_{pre}\),所以取\(\min\)时就一定会取到\(pre\)。
因此\(\forall i\in[l,\text{next}_{pre}),f_i=pre\)。
又因为\(i\geq \text{next}_{pre}\)时取\(\min\)一定不会取到\(pre\)了,所以\(f_i>pre\)。
综上,\(l\)之后的第一个和\(l\)的\(f\)值不同的地方在\(\text{next}_{pre}\)。
于是我们的操作就是:
对每个被更新的\(u\),找出对应的\([l,r]\);
求出\(pre=f_l\),对\([l,\min(\text{next}_{pre}-1,r)]\)区间的\(f\)值进行区间覆盖(全部改成\(f_l\));
令\(l=\text{next}_{pre}\),重复第二步,直到\(l>r\)时结束。
具体实现时我们把所有要处理的\(u\)压到一个vector
里即可。
因为是启发式合并的基础上用线段树修改,所以复杂度\(O(n\log^2 n)\)。
原文:https://www.cnblogs.com/dysyn1314/p/12371882.html