把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF571D】Campus(并查集)

点此看题面

  • 有一个长度为\(n\)的序列,初始全为\(0\)。有两类集合,每一类都有\(n\)个,下标为\(1\sim n\)
  • 要求支持五种操作:合并第一类的两个集合;合并第二类的两个集合;将第一类中一个集合内所有下标对应位置的元素全部加上该集合大小;将第二类中一个集合内所有下标对应位置的元素全部清零;询问一个位置上的值。
  • \(n,m\le5\times10^5\)

并查集

一道非常有趣的题目,算是并查集的一种妙用。

考虑合并集合,首先自然而然就会想到并查集。而这一连串操作看似头疼,但实际上只要不路径压缩,直接启发式并查集即可维护(刚好这题还存在询问一个连通块大小的操作)。

为了防止一个集合原有的修改在合并之后影响到另一个集合,也为了便于清零操作的处理,对于第一类集合的每一个根节点或曾经的根节点,都要维护一个\(vector\)记录所有修改的时间及修改总值的前缀和,并记录它被合并的时间。

对于第二类集合,需要维护以它为根的最大清零时间,也同样需要记录它被合并的时间。

一次询问中,我们首先要求出这个位置最大的清零时间,就是在第二类并查集中一路上跳,求出大于合并时间的最大清零时间。

然后,利用这个最大清零时间,在第一类并查集中一路上跳,与合并时间取较大值,在所有父节点的\(vector\)中二分出有效的修改后缀然后差分得出答案。

具体实现详见代码,应该还是比较简单的。

代码:\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define LL long long
using namespace std;
int n,m;
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	I void readc(char& x) {W(isspace(x=tc()));}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int f0[N+5],g0[N+5],t0[N+5],f1[N+5],g1[N+5],t1[N+5],tg[N+5];vector<pair<int,LL> > v[N+5];
I int fa(int* f,CI x) {return f[x]?fa(f,f[x]):x;}//不路径压缩
I int QT(CI x,CI t) {return max(f1[x]?QT(f1[x],t1[x]):0,tg[x]>=t?tg[x]:0);}//求出大于合并时间的最大清零时间
I LL QS(CI x,CI t)//求出所有修改的总和
{
	LL s=f0[x]?QS(f0[x],max(t,t0[x])):0;if(v[x].empty()||v[x].back().first<t) return s;s+=v[x].back().second;//如果最后的修改都比时间小直接return
	if(v[x][0].first>=t) return s;return s-(--lower_bound(v[x].begin(),v[x].end(),make_pair(t,0LL)))->second;//二分出有效后缀差分
}
int main()
{
	RI Tt,i;for(read(n,Tt),i=1;i<=n;++i) g0[i]=g1[i]=1;
	char op;RI x,y;for(RI T=1;T<=Tt;++T) switch(readc(op),op)
	{
		case 'U':read(x,y),(x=fa(f0,x))^(y=fa(f0,y))&&(g0[x]<g0[y]&&(swap(x,y),0),g0[f0[y]=x]+=g0[y],t0[y]=T);break;//第一类合并
		case 'M':read(x,y),(x=fa(f1,x))^(y=fa(f1,y))&&(g1[x]<g1[y]&&(swap(x,y),0),g1[f1[y]=x]+=g1[y],t1[y]=T);break;//第二类合并
		case 'A':read(x),x=fa(f0,x),v[x].push_back(make_pair(T,(v[x].empty()?0:v[x].back().second)+g0[x]));break;//第一类修改
		case 'Z':read(x),x=fa(f1,x),tg[x]=max(tg[x],T);break;case 'Q':read(x),writeln(QS(x,QT(x,0)));break;//第二类清零;询问(先询问清零时间再询问总修改值)
	}return clear(),0;
}
posted @ 2021-03-31 18:47  TheLostWeak  阅读(58)  评论(0编辑  收藏  举报