zoukankan      html  css  js  c++  java
  • CF571D

    离线并查集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;
    }
    
  • 相关阅读:
    进程池,线程池,协程,gevent模块,协程实现单线程服务端与多线程客户端通信,IO模型
    线程相关 GIL queue event 死锁与递归锁 信号量l
    生产者消费者模型 线程相关
    进程的开启方式 进程的join方法 进程间的内存隔离 其他相关方法 守护进程 互斥锁
    udp协议 及相关 利用tcp上传文件 socketserver服务
    socket套接字 tcp协议下的粘包处理
    常用模块的完善 random shutil shevle 三流 logging
    day 29 元类
    Django入门
    MySQL多表查询
  • 原文地址:https://www.cnblogs.com/Kiana-Kaslana/p/15541069.html
Copyright © 2011-2022 走看看