离线并查集O(nlogn)做法
首先,因为题目中有A,B两个并查集,显然不能同时维护权值。所以我们考虑离线处理。
对于一个查询Query x来说,设当前时间戳为t1,我们只用找到关于x时间戳距离当前最近的清零操作,设它的时间戳是t2,
则一个Add操作(设时间戳为t3)跟当前查询有贡献当且仅当:
t2<t3<t1
然后我们发现答案具有可减性,即我们可以算出t2时刻C[x]的值的值再算出t1时刻C[x]的值,相减即可。
问题就是怎么算t2,我们考虑并查集。
我们对于每个清零操作,显然不可能暴力清零 (废话)
考虑在并查集的根节点打标记,打上当前的时间戳t。每次查询时,从x暴力向上跳(因为并查集按秩合并深度为log级别,所以一次操作复杂度log),取所有的时间戳的max。
对于合并操作,直接按秩合并即可。即深度小的接到深度大的上。
但是也会有问题
比如下面的数据:
Z 1
M 1 2
Q 2
我们打标记的时候由于可能本来的深度大的并查集的根就打了标记,但对新连的没有贡献。而且这个操作不具有可减性。
所以我们对每个点记录一个自己连到父亲的时候的时间戳t2.
然后暴力往上跳的时候顺便维护t2的min,然后当前的t小于t2就没贡献。复杂度(O(nlogn))。
现在我们只剩1,3,4操作了
我们用类似的思路,再次用并查集按秩合并。
我们仍然在根节点打上加法懒惰标记。同样会遇到相同的问题,但这次会好做,因为加法可以差分。我们每次连的时候直接连,设v连向u,则lazy[v]-=lazy[u],就可以直接维护。
查询依然是暴力向上跳。
复杂度O(nlogn)
最慢的点只跑了150ms,这就是小常数吗
代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int f[500005];
int siz[500005];
int dep[500005];
long long val[500005];
long long val2[500005];
long long last[500005];
int Find(int F){
if(f[F]==F)return F;
return Find(f[F]);
}
void _union2(int u,int v){
int f1=Find(u),f2=Find(v);
if(f1==f2)return;
if(siz[f1]<siz[f2])swap(f1,f2);
f[f2]=f1;siz[f1]+=siz[f2];val[f2]-=val[f1];
return;
}
void _union1(int u,int v,int t){
int f1=Find(u),f2=Find(v);
if(f1==f2)return;
if(dep[f1]<dep[f2])swap(f1,f2);
f[f2]=f1;siz[f1]+=siz[f2];dep[f1]=max(dep[f1],dep[f2]+1);val2[f2]=t;
return;
}
int x[500005],y[500005];
long long ans[500005];
char c[500005];
priority_queue<pair<int,int> >Q;
inline int read()
{
int s=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';
ch=getchar();
}
return s*f;
}
signed main(){
n=read();
m=read();
for(int i=1;i<=n;i++)f[i]=i,siz[i]=1,val[i]=0,dep[i]=1;
for(int i=1;i<=m;i++){
c[i]=getchar();
while(c[i]!='U'&&c[i]!='M'&&c[i]!='A'&&c[i]!='Z'&&c[i]!='Q')c[i]=getchar();
if(c[i]=='U'||c[i]=='M')
x[i]=read(),y[i]=read();
else x[i]=read();
}
int G=0;
for(int i=1;i<=m;i++){
if(c[i]=='M'){
_union1(x[i],y[i],i);
}
if(c[i]=='Z'){
val[Find(x[i])]=i;
}
if(c[i]=='Q'){
last[i]=val[x[i]];
int F=x[i];
long long atleast=val2[x[i]];
while(f[F]!=F){
F=f[F];
if(val[F]<atleast){
atleast=max(val2[F],atleast);
continue;
}
atleast=max(val2[F],atleast);
last[i]=max(last[i],val[F]);
}
G++;
Q.push(make_pair(-i,i));
if(last[i]!=0)Q.push(make_pair(-last[i],-i));
}
}
for(int i=1;i<=n;i++)f[i]=i,siz[i]=1,val[i]=0,dep[i]=1;
for(int i=1;i<=m;i++){
if(c[i]=='U')
_union2(x[i],y[i]);
if(c[i]=='A')
val[Find(x[i])]+=siz[Find(x[i])];
while(!Q.empty()&&Q.top().first==-i){
int u=Q.top().second;
Q.pop();
long long xs=1;
if(u<0)u=-u,xs=-1;
int F=x[u];
ans[u]+=xs*val[F];
while(f[F]!=F){
F=f[F];
ans[u]=ans[u]+xs*val[F];
}
}
}
for(int i=1;i<=m;i++)
if(c[i]=='Q')printf("%lld
",ans[i]);
return 0;
}