1. 程式人生 > 其它 >主席樹學習筆記

主席樹學習筆記

主席樹

主席樹,又稱可持久化線段樹,主要思想線上段樹上加上一些歷史節點去維護一些歷史資料,實現對過去資料的查詢。
去盜了一張圖

途中橙色的節點維護的就是歷史資料。

主席樹的用處

引用大佬的一段話:
主席樹就是利用函數語言程式設計的思想來使線段樹支援詢問歷史版本、同時充分利用它們之間的共同資料來減少時間和空間消耗的增強版的線段樹。
其實它是一堆線段樹的集合體。

因為對於一個點的修改,它對整棵樹的影響就是根節點到葉節點的這條長度為\(log~n\)的路徑上的每一個節點,所以它每次修改就在該路徑的節點旁新開\(log~n\)個節點,初始時他們的左右節點的資訊和原來路徑上的點一樣,維護的資訊則變成了需要修改的值,優化了時空的複雜度。

一般我們見到什麼靜態的區間第 k 小(大) 數就可以使用主席樹。

例題: 【洛谷 P3834 【模板】可持久化線段樹 2(主席樹)】

解法&思路:

這是一個很明顯的例題,靜態區間第 k 大數。
還記得樹狀陣列怎麼求的逆序對嗎?它利用的是值域去維護的字首和
我們考慮使用 線段樹去維護區間\([1,1],[1,2],[1,3],·····[1,n-1],[1,n]\)的資訊。
那麼我們每次詢問\([l,r]\)時就可以用\([1,r]-[1,l-1]\)搞定

但是這樣就要搞 n 棵線段樹,空間爆炸,時間複雜度將變成\(O(m*n~log~n)\)
但是線段樹的很多節點是重複的。
所以我們就要用到主席樹了。

首先我們要建一棵空的線段樹。
注意主席樹的節點並不能像線段樹那樣直接求出來,需要去記錄節點的資訊,因為它不再是一棵完全二叉樹了,僅僅只是一顆普普通通的二叉樹。
注意我們以每個點的值域來建樹,然後我們要保證值域的單調遞增,所以要先排序去重,就是離散化。

        scanf("%lld%lld",&n,&m);
	rep(i,1,n) 
	{
		scanf("%lld",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	top=unique(b+1,b+n+1)-b-1; //離散化

建立初始線段樹:

void build(ll &now,ll x,ll y) //初始時建一棵線段樹
{
	now=++cnt; //開新節點
	ll mid=x+y>>1;
	if(x==y) return ;//如果維護的是字首和就要像線段樹那樣去搞
	build(lc[now],x,mid); //建立左子節點
	build(rc[now],mid+1,y); //建立右子節點
}

然後我們要像樹狀陣列求解逆序對那樣対值域進行單點修改。
單點修改:

ll add(ll now,ll x,ll y)
{
	ll nxt=++cnt; //開一個新節點
	lc[nxt]=lc[now],rc[nxt]=rc[now]; //左右子節點的資訊相同
	tree[nxt]=tree[now]+1; //修改維護資訊
	if(x==y) return nxt; //返回現在的節點編號
	ll mid=x+y>>1;
	if(p<=mid) lc[nxt]=add(lc[nxt],x,mid); //維護左子樹
	else rc[nxt]=add(rc[nxt],mid+1,y); //維護右子樹
	return nxt;
}

區間查詢:返回的時答案在離散化陣列中的編號
注意在查詢右子樹時要注意減去當前查詢到的比它大的數的個數

ll ask(ll x,ll y,ll le,ll ri,ll k)
{
	ll ans,val=tree[lc[y]]-tree[lc[x]]; //左子樹兩個區間相減
	if(le==ri) return le; //返回位置
	ll mid=le+ri>>1;
	if(k<=val) ans=ask(lc[x],lc[y],le,mid,k); //比當前值要小,查詢左子樹
	else ans=ask(rc[x],rc[y],mid+1,ri,k-val); 
	//已經有 val 個數比它大了,所以需要減去,在右區間不需要找滿 k 個
	return ans;
}

完整程式碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<vector>
#define r register
#define rep(i,x,y) for(r ll i=x;i<=y;++i)
#define per(i,x,y) for(r ll i=x;i>=y;--i)
using namespace std;
typedef long long ll;
const ll V=2*1e5+10;
ll tree[V<<5],a[V<<5],b[V<<5];
ll lc[V<<5],rc[V<<5],x,y,k;
ll n,m,top,cnt,f[V<<5],ans,p;
void build(ll &now,ll x,ll y)
{
	now=++cnt;
	ll mid=x+y>>1;
	if(x==y) return ;
	build(lc[now],x,mid);
	build(rc[now],mid+1,y);
}
ll add(ll now,ll x,ll y)
{
	ll nxt=++cnt;
	lc[nxt]=lc[now],rc[nxt]=rc[now];
	tree[nxt]=tree[now]+1;
	if(x==y) return nxt;
	ll mid=x+y>>1;
	if(p<=mid) lc[nxt]=add(lc[nxt],x,mid);
	else rc[nxt]=add(rc[nxt],mid+1,y);
	return nxt;
}
ll ask(ll x,ll y,ll le,ll ri,ll k)
{
	ll ans,val=tree[lc[y]]-tree[lc[x]];
	if(le==ri) return le;
	ll mid=le+ri>>1;
	if(k<=val) ans=ask(lc[x],lc[y],le,mid,k);
	else ans=ask(rc[x],rc[y],mid+1,ri,k-val);
	return ans;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	rep(i,1,n) 
	{
		scanf("%lld",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	top=unique(b+1,b+n+1)-b-1;
	build(f[0],1,top);
	rep(i,1,n)
	{
		p=lower_bound(b+1,b+top+1,a[i])-b;
		f[i]=add(f[i-1],1,top);
	}
	rep(i,1,m)
	{
		scanf("%lld%lld%lld",&x,&y,&k);
		ans=ask(f[x-1],f[y],1,top,k);
		printf("%lld\n",b[ans]);
	}
	return 0;
}