BZOJ3674 可持久化并查集加强版

分析

要做的其实就是实现一个可持久化数组以及按秩合并。

如何实现可持久化数组?如果每次都复制一遍那肯定时空都不行。想到用二分实现操作,每次只修改一个点,那么可以用类似线段树一样的区间覆盖复制原fa数组满足要求,每次把未变的复制一下,只改变要变的那条树链就行了。而既然与线段树如此类似,我们就直接用一个线段树加一个可持久化线段树就行了。由于是按秩合并,不带路径压缩,所以find查询的时候有两个\(\log\)。线段树上层的节点其实就是个索引而已,它们的fa和dep没有实际用处。

时间复杂度\(O(m\log^2n+n\log n)\),空间复杂度\(O(m\log n+n\log n)\)

代码

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
	int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;

int n;

struct PreSegTree
{
	int L,R;
	int fa;
	int dep;
}PST[4400000]; // 4321928.0948873623478703194294894
int root[200007],pcnt;

void build(int&now,int l,int r)
{
	if(!now)
		now=++pcnt;
	if(l==r)
	{
		PST[now].fa=l;
//		cerr<<now<<"fa ="<<PST[now].fa<<endl;
		return;
	}
	int mid=(l+r)>>1;
	build(PST[now].L,l,mid);
	build(PST[now].R,mid+1,r);
}

int query(int now,int l,int r,int p)
{

//	cerr<<"querying "<<now<<' '<<l<<" "<<r<<" "<<p<<endl;
	if(l==r)
		return now;
	int mid=(l+r)>>1;
	if(p<=mid)
		return query(PST[now].L,l,mid,p);
	else if(p>=mid+1)
		return query(PST[now].R,mid+1,r,p);
}

void modify(int&now,int l,int r,int p,int v)
{
	PST[++pcnt]=PST[now],now=pcnt;
	if(l==r)
	{
		PST[now].fa=v;
		return;
	}
	int mid=(l+r)>>1;
	if(p<=mid)
		modify(PST[now].L,l,mid,p,v);
	else if(p>=mid+1)
		modify(PST[now].R,mid+1,r,p,v);
}

void add(int now,int l,int r,int p)
{
//	PST[++pcnt]=PST[now],now=pcnt;
	if(l==r)
	{
		++PST[now].dep;
		return;
	}
	int mid=(l+r)>>1;
	if(p<=mid)
		add(PST[now].L,l,mid,p);
	else if(p>=mid+1)
		add(PST[now].R,mid+1,r,p);
}

int find(int now,int p)
{
	int x=query(now,1,n,p);
//	cerr<<"query="<<x<<" fx="<<PST[x].fa<<" p="<<p<<endl;
	if(PST[x].fa==p)
		return x;
	return find(now,PST[x].fa);
}

int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
	int m;
	read(n);read(m);
	build(root[0],1,n);
	int ans=0;
	for(int i=1;i<=m;++i)
	{
		int opt;
		read(opt);
		if(opt==1)
		{
			int a,b;
			read(a);read(b);
			a^=ans,b^=ans;
			root[i]=root[i-1];
			int fx=find(root[i],a),fy=find(root[i],b);
//			cerr<<"result="<<fx<<" "<<fy<<endl;
//			cerr<<"fa="<<PST[fx].fa<<" "<<PST[fy].fa<<endl;
			if(PST[fx].fa==PST[fy].fa)
				continue;
			if(PST[fx].dep>PST[fy].dep)
				swap(fx,fy);
			modify(root[i],1,n,PST[fx].fa,PST[fy].fa);
			if(PST[fx].dep==PST[fy].dep)
				add(root[i],1,n,PST[fy].fa);
		}
		else if(opt==2)
		{
			int k;
			read(k);
			k^=ans;
			root[i]=root[k];
		}
		else if(opt==3)
		{
			int a,b;
			read(a);read(b);
			a^=ans,b^=ans;
			root[i]=root[i-1];
			int fx=find(root[i],a),fy=find(root[i],b);
//			cerr<<"result="<<fx<<" "<<fy<<endl;
//			cerr<<"fa="<<PST[fx].fa<<" "<<PST[fy].fa<<endl;
			if(PST[fx].fa==PST[fy].fa)
				ans=1;
			else
				ans=0;
			printf("%d\n",ans);
		}
	}
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

Hint

为什么modify的时候要新开节点而add的时候不开?

首先不开就是修改历史版本,开了就是新建的节点。modify要开是可持久化基本要求,add按道理也应该开。只不过add对dep的修改其实不影响历史版本,因为合并的时候无论dep如何都是要合并的。总之这样做是正确的,减少了空间,但是要增加时间。

posted on 2018-08-24 15:02  autoint  阅读(180)  评论(0编辑  收藏  举报

导航