首页 > 其他 > 详细

题解 zr1212 【20WC集训】货币

时间:2020-02-27 15:01:16      阅读:77      评论:0      收藏:0      [点我收藏+]

题目链接

我们给每个连通块图上一种颜色。不同的连通块涂不同的颜色。

首先,我们定义\(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)\)

参考代码

题解 zr1212 【20WC集训】货币

原文:https://www.cnblogs.com/dysyn1314/p/12371882.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!