1. 程式人生 > >關於可持久化並查集的學習和思考

關於可持久化並查集的學習和思考

鑑於noip比賽前集訓時SAKER前輩教了我這個蒟蒻可持久化線段樹以來,我懂得了如何維護一個支援歷史查詢的線段樹。於是我就開始異想天開了:可不可以快速維護一個支援歷史查詢的陣列呢?

就在這時,我上網看到了一個新的演算法:可持久化並查集。先用例題來講吧:

BZOJ3674:可持久化並查集加強版

Description
自從zkysb出了可持久化並查集後……
hzwer:亂寫能AC,暴力踩標程
KuribohG:我不路徑壓縮就過了!
ndsf:暴力就可以輕鬆虐!
zky:……

n個集合 m個操作
操作:
1 a b 合併a,b所在集合
2 k 回到第k次操作之後的狀態(查詢算作操作)
3 a b 詢問a,b是否屬於同一集合,是則輸出

1否則輸出0
請注意本題採用強制線上,所給的a,b,k均經過加密,加密方法為x = x xor lastans,lastans的初始值為0
0<n,m<=2*10^5

題目分析:

本題要求強制線上操作,於是我們很容易想到暴力:維護m個大小為n的fa陣列,在每做完一步後都記錄一下每個點的fa,(可以使用路徑壓縮),然後該返回第k步的時候就返回那個陣列,時間為O(n*m)。

然而這樣會超時,炸空間。要在規定時間內求解我們有三種演算法:

一.     分塊(這不是本文要重點討論的演算法)

分塊演算法是一種比較常見的演算法。對於本題,我們只需要維護sqrt(m)個大小為n的fa陣列,分別記錄在完成sqrt(m),2*sqrt(m)……m步後fa陣列的狀態。然後要跳回去的時候只需要O(sqrt(m))的時間就行了,這樣可以使用路徑壓縮。時空複雜度均為O(m*sqrt(m))左右。

二.     可持久化線段樹,且不使用路徑壓縮。

我們來看一下,如果不使用路徑壓縮的話,對於每一次合併,我們只會改變fa陣列中的一個值(注意:每一次改的地方都不同),其他的值都和它們的歷史版本一樣:


也就是說,我們要快速地讓13,5n快速地指向它的歷史版本。不使用路徑壓縮的可持久化並查集,本質上就是要維護一個支援單點修改和歷史版本查詢的陣列

經過在下翻閱眾多網上大神的做法,他們基本上都開了一棵可持久化線段樹,這棵線段樹的非葉子節點i只負責維護lsonrson,不維護除此之外的任何資訊,它的作用是方便我們在修改的時候快速地將[Li,Ri]這一段O(1)地指向它的歷史版本

我們可以使用啟發式合併,這樣找一個節點所在集合的根,就需要查詢log(n)次線段樹,而每一次時間都是log(n),故時間為O(m*log^2(n)),空間為m*log(n)。

接下來就是本人的一些思考:時間為O(m*log^2(n)),線段樹的常數又這麼大,時間和分塊沒什麼區別呀。該線段樹每一次查詢保證是log(n)的,每一次查詢只有查詢到葉子節點才能得出答案,能不能讓常數小一點呢?

我們先假設用連結串列儲存fa陣列,這樣當我們修改fa[x]的值的時候,我們需要O(x)的時間與和時間成正比的空間,如圖:


我們要修改fa[3],就要一路開新的節點,最後讓fa[3]的後繼指向4。

那麼問題很明顯了,我們要在不改變連結串列儲存方式的前提下,儘可能快速地到達任意一個節點,於是本人想到可以把這個連結串列變得“緊湊”一些,成為一棵樹:


注意,這和線段樹不同,它的每一個節點就是原連結串列中的一個元素。這樣我們要修改元素x的時候,時空均為O(floor(log(x))+1)。

那麼怎樣走才能到5號節點呢?我們發現(5)10=(101)2,故最開始是1,即為一號節點,第二位是0,故往左子樹走,接下來是1,走右子樹。我們可以發現,2的子樹轉化成二進位制位後都是10開頭的,因為2往下走一層,無非就是在2的二進位制位後面加了一個0或1,故這樣走正確性顯然。

然而這只是常數級別的優化罷了,相當於線段樹的常數變成樹狀陣列的常數而已。

三.     使用路徑壓縮。

理論時空是O(m*n*log(n)),然而實際執行只比啟發式合併慢那麼一點點……

附bzoj3674CODE(已AC):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=200100;
const int maxl=20;

struct data
{
	int lson,rson,id,val,_Size;
} tree[maxn+2*maxn*maxl];
int Root[maxn];
int cur;

int w[maxn];
int n,m,lt,nt,lans;

void Build(int x)
{
	tree[x].id=x;
	tree[x].val=x;
	tree[x]._Size=1;
	
	int left=x<<1;
	if (left<=n)
	{
		tree[x].lson=left;
		Build(left);
	}
	
	int right=left|1;
	if (right<=n)
	{
		tree[x].rson=right;
		Build(right);
	}
}

data Query(int root,int L,int x)
{
	if (x==L) return tree[root];
	if (x&(1<<(w[x]-w[L]-1))) return Query(tree[root].rson,L*2+1,x);
	else return Query(tree[root].lson,L*2,x);
}

data Find_fa(int x)
{
	data y=Query(Root[lt],1,x);
	while (y.id!=y.val) y=Query(Root[lt],1,y.val);
	return y;
}

void Update(int root,int L,int x,int nid,int v)
{
	if (L==x)
	{
		if (nid==0) tree[root].val=v;
		else tree[root]._Size=v;
		return;
	}
	if (x&(1<<(w[x]-w[L]-1)))
	{
		data temp=tree[ tree[root].rson ];
		tree[root].rson=++cur;
		tree[cur]=temp;
		Update(cur,L*2+1,x,nid,v);
	}
	else
	{
		data temp=tree[ tree[root].lson ];
		tree[root].lson=++cur;
		tree[cur]=temp;
		Update(cur,L*2,x,nid,v);
	}
}

void Add(int x,int y)
{
	data fx=Find_fa(x);
	data fy=Find_fa(y);
	
	if (fx.val==fy.val)
	{
		Root[nt]=Root[lt];
		return;
	}
	
	if (fx._Size>fy._Size) swap(fx,fy);
	
	Root[nt]=++cur;
	tree[cur]=tree[ Root[lt] ];
	Update(cur,1,fx.id,0,fy.id);
	
	tree[++cur]=tree[ Root[nt] ];
	Root[nt]=cur;
	Update(cur,1,fy.id,1,fx._Size+fy._Size);
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%d%d",&n,&m);
	
	Build(1);
	
	Root[1]=1;
	cur=n;
	lt=1,nt=1;
	lans=0;
	
	w[1]=1;
	for (int i=2; i<maxn; i++) w[i]=w[i/2]+1;
	
	/*for (int i=1; i<=n; i++)
	{
		data temp=Find_fa(i);
		printf("%d ",temp.val);
	}
	printf("\n");*/
	
	for (int i=1; i<=m; i++)
	{
		int op;
		scanf("%d",&op);
		
		if (op==1)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			a=a^lans;
			b=b^lans;
			
			nt++;
			Add(a,b);
			lt=nt;
		}
		
		if (op==2)
		{
			int k;
			scanf("%d",&k);
			k=k^lans;
			
			lt=k+1;
			nt++;
			Root[nt]=Root[lt];
			lt=nt;
		}
		
		if (op==3)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			a=a^lans;
			b=b^lans;
			
			//printf("%d %d\n",a,b);
			
			data fa=Find_fa(a);
			data fb=Find_fa(b);
			bool f=(fa.val==fb.val);
			
			printf("%d\n",f);
			lans=f;
			
			nt++;
			Root[nt]=Root[lt];
			lt=nt;
		}
		
		/*for (int i=1; i<=n; i++)
		{
			data temp=Find_fa(i);
			printf("%d ",temp.val);
		}
		printf("\n");*/
	}
	
	return 0;
}