1. 程式人生 > >數據結構之主席樹

數據結構之主席樹

思想 soft 區間 容易 效果 加減 接受 href bad

這裏先講靜態的主席樹,關於靜態區間第k小。(有興趣的朋友還可以去看看我寫的整體二分,代碼實現略優於主席樹我覺得,當然靜態主席樹是很好寫的)

題目描述:

題目描述

如題,給定N個正整數構成的序列,將對於指定的閉區間查詢其區間內的第K小值。

輸入輸出格式

輸入格式:

第一行包含兩個正整數N、M,分別表示序列的長度和查詢的個數。

第二行包含N個正整數,表示這個序列各項的數字。

接下來M行每行包含三個整數 l, r, kl,r,k , 表示查詢區間 [l, r][l,r] 內的第k小值。

輸出格式:

輸出包含k行,每行1個正整數,依次表示每一次查詢的結果

輸入輸出樣例

輸入樣例#1:
5 5
25957 6405 15770 26287 26465 
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
輸出樣例#1:
6405
15770
26287
25957
26287

那麽我們明確主席樹是個什麽東西。它就是,可持久化線段樹。
首先我們不考慮主席樹,而是對於這個區間第k小問題做一個分析。如果我們對每一個區間暴力快排,毋庸置疑,絕對炸上天,T到你想哭。
於是聰明的前人發現了這個問題的一個特點,下面我來說說。
先把每一個數值離散化,樹中每一個值就即是權值也是排名了(離散化建議看看我之前寫的離散化博客裏的代碼)。好吧我到現在還沒有說明這個樹到底是個什麽玩意兒。

發明者的原話:“對於原序列的每一個前綴[1···i]建立出一棵線段樹維護值域上每個數出現的次數,則其樹是可減的”

可以加減的理由:主席樹的每個節點保存的是一顆線段樹,維護的區間信息,結構相同,因此具有可加減性(關鍵)

是的,我們對於每一個前綴建一顆樹,每一段區間維護的是當前區間的點的個數,註意這裏的區間不是位置的區間,而是權值的區間,

這就是為什麽要離散化了。

那麽減可以幹什麽呢?註意這是個很重要的思想,前綴和。

先考慮一個問題,如果每次詢問的l都是數組一開頭的位置1,是不是很容易維護?

下面舉一個例子:維護數組6 2 3 1 4 5。 對前綴1~6建樹 (圖難看。。。不打緊的咳咳),這個求第k小應該一目了然吧

技術分享圖片

然而,區間第k小其實很容易,對於區間[l,r]而言,只需要在每個節點用前綴r的節點值減去前綴l-1的節點值就好了,其他的和上面是一樣的

下面附上一組大佬的圖

技術分享圖片技術分享圖片技術分享圖片技術分享圖片

註意和我的數據是不一樣的,他的是4 1 1 2 8 9 4 4 3

然後讀者按照我剛剛說的用前綴r的樹的每一個節點減去對應的前綴l-1的樹的節點得到一顆新的樹就好

好的,現在我們回到原來的問題,主席樹。其實上面就是主席樹,但是若是我們對於每一個前綴都暴力建一次樹的話,在時間和空間上都不能接受。

這時候我們註意到相鄰的兩棵樹其實是很相似的,我們可以讓這一棵樹和上一棵樹共用一些節點,從而達到減低空間和時間復雜度的效果。前綴每次向右一位,其實就是多插入了一個數值,新的

樹和上一棵樹的唯一區別其實就是一條鏈上變了而已。

事實證明,這種方法十分的有效。

技術分享圖片

就像這樣,在原來的基礎上加上幾個點

下面我附上我的代碼供大家研究,其實我認為代碼更加好懂

#include<bits/stdc++.h>
using namespace std;

const int maxn=2e5+15;
int n,m,cnt;
int a[maxn],b[maxn],tree[maxn<<5],L[maxn<<5],R[maxn<<5],sum[maxn<<5];
int build(int l,int r)
{
	int rt=++cnt;
	sum[rt]=0;
	if (l<r)
	{
		int mid=(l+r)>>1;
		L[rt]=build(l,mid);
		R[rt]=build(mid+1,r);
	}
	return rt;
}
int update(int pre,int l,int r,int x)
{
	int rt=++cnt;
	L[rt]=L[pre];R[rt]=R[pre];sum[rt]=sum[pre]+1;//多插入了點於是加個1 
	if (l<r)
	{
		int mid=(l+r)>>1;
		if (x<=mid) L[rt]=update(L[pre],l,mid,x);//看看插到哪一邊,另一邊其實是一樣的 
		else R[rt]=update(R[pre],mid+1,r,x);
	}
	return rt;
}
int query(int u,int v,int l,int r,int k)
{
	if (l>=r) return l;
	int x=sum[L[v]]-sum[L[u]];//減一下就好 
	int mid=(l+r)>>1;
	if (x>=k) return query(L[u],L[v],l,mid,k);
	else return query(R[u],R[v],mid+1,r,k-x);
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+1+n);//離散化 
	int q=unique(b+1,b+1+n)-b-1;
	tree[0]=build(1,q);
	for (int i=1;i<=n;i++)
	{
		int t=lower_bound(b+1,b+1+q,a[i])-b; 
		tree[i]=update(tree[i-1],1,q,t);//通過上一個樹建樹 
	}
	while (m--)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		int t=query(tree[x-1],tree[y],1,q,z);//通過x-1樹和y樹相減 
		printf("%d\n",b[t]);
	}
	return 0;
}

  上文部分圖來自大佬 Lpy_Now,感謝大佬

數據結構之主席樹