1. 程式人生 > >個人對主席樹演算法的理解

個人對主席樹演算法的理解

首先借主席樹發明人的一段話:

..這個東西是當初我弱不會劃分樹的時候寫出來替代的一個玩意..被一小撮別有用心的人取了很奇怪的名字> <
想法是對原序列的每一個字首[1..i]建立出一顆線段樹維護值域上每個數的出現次數,然後發現這樣的樹是可以減的,然後就沒有然後了

轉載請註明出處,謝謝。

首先定義:

a[MAXN],a2[MAXN];
struct node
{
	node *ch[2];
	int siz;
	node(){ch[0]=ch[1]=NULL;siz=0;}
	node(node *_ch0,node *_ch1,int _siz):siz(_siz){ch[0]=_ch0,ch[1]=_ch1;}
	void update()
	{
		if (ch[0]) siz+=ch[0]->siz;
		if (ch[1]) siz+=ch[1]->siz;
	}
}*null=new node(),*root[MAXN]={NULL};


自己理解:

1:前提條件:有N個數字,size個不同的數字。

2:用a[N]儲存原來的數字,a2[size]儲存排好序的數字;

3:也就是每一個點root【i】都維護著size大小的線段樹,而該線段樹維護的資訊為:在a[1] 到a[i]數字集合中,size種數字分別出現的次數。

4:root[i] - > ch[0] 儲存的是排好序,前size/2種數字分別出現的次數,root[i] - > ch[1] 儲存的是排好序,後size/2種數字分別出現的次數;

值得注意的是,並不是每個root【i】都必須重新開闢size * log2(size)的空間,比如當新加入的a【i】非常小,那麼root[i] ->ch[1]後size/2種數字分別出現的次數

相對於root[i - 1] -> ch[1]是不會變。所以可以只用有孩子指標指向root[i - 1] -> ch[1]所指的節點就可以共用。當新加入的a【i】非常大的時候,那麼ch【0】就可以共用。

建樹的關鍵程式碼:

void make_node(node *&y,node *&x,int l,int r,int t)
{
	if (x==NULL) x=null;
	y=new node();
	int m=(l+r)>>1;
	if (l==r)//已經到達葉子節點,
	{
		*y=*x;
		y->siz++;
		return;
	}
	if (t<=a2[m]) 
	{
		make_node(y->ch[0],x->ch[0],l,m,t);
		y->ch[1]=x->ch[1];
		y->update();
	}
	else
	{
		make_node(y->ch[1],x->ch[1],m+1,r,t);
		y->ch[0]=x->ch[0];
		y->update();
	}
}

為了防止new爆T,那可以先用陣列存好。

一: 先分析空間複雜度:

每加入一個a【i】時,就會增加log2(size)個新節點,也就是每一層會產生一個新節點。所以空間複雜度為:N * log2(size)

二:當檢視一個一個區間中的 第K大值時

在第一層時:

1.先檢視這段區間在前size/2個的出現次數是否大於k,如果是,那麼第k個,肯定在前size/2中,否則在後size/2中,

那麼第k大,就得減去前size/2個出現的次數之和,變為在後size/2種數字中求第(k - 前size/2個出現的次數之和)大

2. 而前size/2個的出現次數可以直接相減可得,比如要看區間L到R,前size/2種數字出現的次數之和,

等於(root[R] - > ch[0] -> size) - (root[L - 1] -> ch[0] - >size)

3.往下層走,只不過就是size變為了size/2,遞迴下去就可以了。

很容易看出查詢一次的時間複雜度為log2(size);

關鍵程式碼如下:

void find(node *&x1,node *&x2,int l,int r,int k)
{
	if (x1==NULL) x1=null;
	if (x2==NULL) x2=null;
	if (l==r) {printf("%d\n",a2[l]);return;}
	int m=(l+r)>>1;
	int ls=0;
	if (x2->ch[0]) ls+=x2->ch[0]->siz;
	if (x1->ch[0]) ls-=x1->ch[0]->siz;
	
	if (ls>=k) find(x1->ch[0],x2->ch[0],l,m,k);
	else find(x1->ch[1],x2->ch[1],m+1,r,k-ls); 
}

下面是網上轉載的主席樹完整程式碼: