# 淘淘的集合

题意:

淘淘有\(n\)个数字\(a[1]...a[n]\)\(n\)个集合,一开始所有的\(a[i]\)都是\(0\),第\(i\)个集合初始为\(i\).

淘淘有时候会合并两个集合或者给一个集合里的所有数整体加上一个值.

蓝蓝觉得淘淘的数字太大了,有时会把\(a[l..r]\)都赋值为0,有时会询问一段区间的数字的和。

形式化的讲,就是有四种事件会发生:

1 x y合并数字\(x\)所在的集合和\(y\)所在的集合.如果\(x\)\(y\)已经在同一个集合,则没有变化;

2 x v\(x\)所在的集合为\(S\),对于任意\(y∈S,a[y]+=v\)

3 l r\(a[l]...a[r]\)都赋值为\(0\);

4 l r询问\(a[l]...a[r]\)的和.

一共会发生\(m\)次这样的事件,你能正确解答蓝蓝的每次询问吗?


解:

首先观察题面,上有写"保证事件\(4\)\((r−l+1)\)的和不超过\(1e7\)."

这就说明,区间是假区间,我们只用实现能以很小的复杂度单点查询就可以了

考虑没有操作3的情况

此时,前两项操作可以轻易由并查集维护,

集合整体加时就给代表元打上一个加tag

合并集合时就启发式,将小的集合每个点拆出来,数值加上tag仍到大的里,

另外,对于操作4,区间拆成单点查询,查到点的值,再找其集合代表元,加上tag就是现在值了

问题来了,如何维护操作3呢?

每个点直接赋0,立马爆炸

所以考虑这样一种解决办法:

我们记录每个点每次询问之前最近一次被赋0的时间

我们不将它真正赋0,而是用查询时这个点的值减去最近一次被赋0时这个点的值,

这样,得出的结果就等价于查询其被赋0之后的真实值

那么,如何维护每次询问前最近一次赋0时间呢?

这就要有一个支持区间赋值,单点查询的数据结构

每次赋0操作,给每个点赋当前的时间戳,询问时单点查对应值就完了

然后第一想到的线段树不行,因为大量的单点查会导致标记大量下放,线段树的复杂度就假了

所以就想到让分块来干这个事

问题解决了


重新捋思路,考虑实现

1、循环读入操作,先离线

2、枚举一遍操作,先不管操作1和2

2.1、若操作为赋0,分块上打上现在的时间戳

2.2、若为查询,块上查单点,在时间轴上赋0的地方打上一个标记

因为同一时间有很多这样的标记,所以vector

3、再循环一次操作

3.1、若类型为1或2,按照并查集直接做

3.2、若为3,看看时间轴上,有哪些点要查

若需要查点x,先查出来其值为val,然后在一条数组\(tmpans\)上令\(tmpans[x]=-val\)

3.3、若为4,区间拆单点挨个查,另建ans=0

对于查点x,先查出来其值为val,然后\(ans+=val+tmpans[x],\ tmpans[x]=0\)

最后,输出当前区间查询的结果ans就完了

posted @ 2020-08-22 20:33  熹圜  阅读(155)  评论(0)    收藏  举报