CF571D Campus
Link
实际上我们是维护了两个森林。
合并两个点时,我们考虑新建一个点,用这个新建的点储存信息,然后把需要合并的两个点所在树的根连到这个点上。
如果仅有大学的操作,我们可以每次给修改的树的根打一个,那么询问的就是一个点到树根的路径上的权值和,这个可以用带权并查集实现。
现在加上了军队的操作,如果我们知道一个点最后被覆盖的时间,那么我们就可以将大学的操作离线+差分解决。这个可以类似地用带权并查集实现。
#include<cstdio>
#include<cctype>
#include<vector>
#include<cstring>
#include<numeric>
#include<algorithm>
using i64=long long;
const int N=1000007;
int fa[N],vis[N],size[N];i64 tag[N],ans[N];
struct node{int o,t,x,y,id;};std::vector<node>vec;
int read(){int x=0,c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
char get(){char c=getchar();while(!isupper(c))c=getchar();return c;}
int find(int x,int f)
{
if(fa[fa[x]]==fa[x]) return fa[x];
int y=find(fa[x],f); return tag[x]=f? std::max(tag[x],tag[fa[x]]):tag[x]+tag[fa[x]],fa[x]=y;
}
int main()
{
int n=read(),m=read(),cnt=n;
std::iota(fa+1,fa+n+n+1,1);
for(int i=1,x,y;i<=m;++i)
switch(get())
{
case 'U':x=read(),y=read(),vec.push_back({0,i,x,y,0});break;
case 'M':x=read(),y=read(),++cnt,fa[find(x,1)]=fa[find(y,1)]=cnt;break;
case 'A':vec.push_back({-1,i,read(),0,0});break;
case 'Z':tag[find(read(),1)]=i;break;
case 'Q':
x=read(),vis[i]=1,vec.push_back({1,i,x,1,i}),y=find(x,1);
if(tag[x]||tag[y]) vec.push_back({1,(int)std::max(tag[x],tag[y]),x,-1,i});;
break;
}
std::sort(vec.begin(),vec.end(),[](const node&a,const node&b){return a.t<b.t;}),std::iota(fa+1,fa+n+n+1,1),std::fill(size+1,size+n+n+1,1),memset(tag+1,0,n<<4),cnt=n;
for(auto t:vec)
switch(t.o)
{
case -1:tag[find(t.x,0)]+=size[find(t.x,0)];break;
case 0:t.x=find(t.x,0),t.y=find(t.y,0),++cnt,size[cnt]=size[t.x]+size[t.y],fa[t.x]=fa[t.y]=cnt;break;
case 1:find(t.x,0),ans[t.id]+=t.y*(tag[t.x]+(fa[t.x]==t.x? 0:tag[fa[t.x]]));
}
for(int i=1;i<=m;++i) if(vis[i]) printf("%lld\n",ans[i]);
}