• 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;
    }
    
  • 相关阅读:
    大一训练赛20181105-二分三分分治部分
    该说命运弄人,毫不留情。
    矩阵快速幂模板
    Final Destination II -- 矩阵快速幂模板题
    UVA -580 组合数学
    NYOJ-16-矩形嵌套 记忆化搜索
    封装标签省,市,县。三级联动
    java压缩图片设置宽高
    sql分页
    常用的正则表达式@java后台
  • 原文地址:https://www.cnblogs.com/Kiana-Kaslana/p/15541069.html
Copyright © 2020-2023  润新知