1. 程式人生 > 實用技巧 >主席樹

主席樹

這個東西對於我這種菜雞來說還是有一點難以把他解釋清楚,所以在一些關鍵的講解步驟(我自己解釋不清楚的地方),我就只好引用一下一些大佬的!(其實寫完了以後發現這些東西都是我自己寫的!!!嗯,有進步!!!)

下面我就開始講哈!

相信大家看這篇講解之前都應該看了我前面寫的一篇部落格,如果沒有看大家可以先去看一下:

講解傳送門(求整體區間第k大)

好了我們預設大家應該已經懂得了如何求整體區間第k大了, 其實你只要學會這個了主席樹已經就比較簡單了,那麼我們下面就來講解一下主席樹和這個東西的一些區別,主席樹說白了就是比這個東西多了一個我們可以查區間中的第k大,那麼這個操作如何實現呢?
是不是我們要把每個區間都建一棵樹呢?其實沒有這個必要,我們還是按照字首建樹,這樣會從n方的空間複雜度成功減少到n的複雜度,但是這樣很明顯空間上還是不滿足要求,但是我們同樣又發現其實如果我們這樣進行建樹會有很多地方有重複,所以我們其實可以充分的利用一下這些重複使用的空間,也就是說我們下一次建樹就沒有必要再重新建這一部分了,我們只是需要把這部分沒有的部分重新建進去(也就是每一次多建一條鏈進入樹中),然後把每一個節點記錄下來(開一個數組進行記錄),然後建樹的這一部分我們就簡單而粗暴的講解完了。

下面給大家一個建樹的程式碼(不需要完全理解,有一些細節每一個人處理的都不一樣):

void ins(int k,long long l,long long r,int val) { 
	a[k].l=l,a[k].r=r;
	if(l==r) { 
		a[k].cnt++;return;
	} 
	long long mid=l+r;mid/=2;
	if(val<=mid) { 
		cnt++;
		a[cnt]=a[ls(k)];
		ls(k)=cnt;
		ins(ls(k),l,mid,val);
	} 
	else { 
		cnt++;
		a[cnt]=a[rs(k)];
		rs(k)=cnt;
		ins(rs(k),mid+1,r,val);
	} 
	update(k);
} 

然後建完樹了以後,我們就要進行處理操作了,這部分操作其實才是整片程式碼中最難理解的地方,大家一定要認真的理解一下,我這裡儘量用最少的話語把大家講懂!想要理解這一部分內容請確保你一定是理解到了我們每一次建樹其實只是往樹中增加(修改)一條鏈,所以說如果說我們要查詢i到j之間的第k大,那麼這棵樹從哪裡來呢?其實就是我們把j的字首樹減去i的字首樹就是ij區間的樹,所以如果我們用j左子樹的大小減去i左子樹的大小那麼我們就成功的求出了ij樹左子樹的大小,然後我們就可以進行與上一篇講解同樣的事情了,如果我們要找的k大於左子樹,那麼我們就找右子樹,否則繼續找左子樹,最後就找到了最小,簡不簡單!!!(還是不懂的繼續往下看,careful!!!)

如果你還是沒有看懂,那麼我在這裡給你兩個小小的提示,首先,你要注意,為什麼這裡的兩個樹可以互相的進行減呢?是因為,這個樹作為一個權值線段樹他是長得一模一樣的。所以我們相減之後其實那些剩餘的桶就是在這個區間中增加的元素個數!!!其次,你也可能會問,為什麼query中必須寫成右樹的左邊等於右樹的右邊,其實是這樣的,因為我們是使用動態開點來建造的權值線段樹,所以我們不敢保證左邊的樹一定會有右邊樹的節點(因為有可能還沒有建造當前節點)哈哈哈,懂了吧!不懂就不是我的錯了!!!

下面上一個程式碼:

#include<bits/stdc++.h>
using namespace std;
struct sd{
	int l,r,son[2],cnt;
};
const int Max=1e9+7;
const int maxn=2e5+10;
int cnt=1;
int n,m;
int rot[maxn],num[maxn];
sd node[maxn*50];
void update(int k)
{
	node[k].cnt=node[node[k].son[0]].cnt+node[node[k].son[1]].cnt;
}
void modify(int k,long long l,long long r,int val)
{
	node[k].l=l;node[k].r=r;
	if(l==r)
	{
		node[k].cnt++;return;
	}
	long long mid=l+r;mid/=2;
	if(val<=mid)
	{
		cnt++;
		node[cnt]=node[node[k].son[0]];
		node[k].son[0]=cnt;
		modify(node[k].son[0],l,mid,val);
	}
	else
	{
		cnt++;
		node[cnt]=node[node[k].son[1]];
		node[k].son[1]=cnt;
		modify(node[k].son[1],mid+1,r,val);
	}
	update(k);
}
int query(int rl,int rr,int val)
{
	if(node[rr].l==node[rr].r) return node[rr].l;//very very important
	int delta=node[node[rr].son[0]].cnt-node[node[rl].son[0]].cnt;
	if(val<=delta)
	return query(node[rl].son[0],node[rr].son[0],val);
	else
	return query(node[rl].son[1],node[rr].son[1],val-delta);
}
int main()
{
	std::ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>num[i]; 
	}
	rot[0]=1;
	for(int i=1;i<=n;++i)
	{
		cnt++;
		rot[i]=cnt;node[rot[i]]=node[rot[i-1]];//複製一個新的節點 
		modify(rot[i],0,2*Max,num[i]+Max);
	}
	int a,b,c;
	for(int i=1;i<=m;++i)
	{
		cin>>a>>b>>c;
		cout<<query(rot[a-1],rot[b],c)-Max<<endl;
	}
	return 0;
}

謝謝採納!!!