HDU 2665 Kth number(區間第K大) (離散化+主席樹)

題意就是給定一個無序數列,然後給定一堆詢問,求區間[ s , t ]內的第k大的數。

查了很多資料,方法很多,看了半天只看懂了主席樹怎麼做,其餘的做法以後補充。

主席樹(函數式線段樹):

首先是離散化,將所有數按升序映射到正整數。

用線段樹統計這些正整數的出現次數(Sum數組),這樣就可以快速找到第k大的數。

開始時,是一顆空樹,所有的Sum都爲0.

用T[n]表示用數列前n個數按上述方法建立的線段樹的根(以後用根來指代整棵樹)。

然後要求[S,T]區間的按值建立的線段樹,就只需要把兩顆線段樹T[R]和T[S-1]相減(因爲葉節點表示的是數出現的次數,滿足區間減法)。

永久化數據結構思想:

永久化數據結構思想就是在修改的時候,不修改原來數據結構,而是新建節點,並且最大限度的複用原來的數據結構。

由於T[n]和T[n-1]之間結構一樣並且,只有一個葉節點的值不同,所以最多有log2 (n) 個節點不同,於是相同的節點可以複用。

就是T[n]只需要多用改變的節點的空間,其餘的直接使用前一棵樹的節點,這樣做的好處是保存了每個時間的線段樹狀態,也就是保存了整個操作歷史。

由於沒有破壞原來的數據結構,可以訪問任意時刻的線段樹(從根開始遞歸)。要做到這點,就沒辦法再用原來線段樹的堆式存儲了,而是增加了左右子樹的指針。

也可以仿照SBT的寫法直接用數組表示節點,用L[]和R[]表示左右子樹的下標。

建樹與查詢:

求T[n]的時候就是在T[n-1]中遞歸,然後把所有經過的節點(也就是修改過的節點)都用新節點存起來。


最後在遞歸訪問線段樹的時候,同步訪問R 和 S-1兩顆線段樹,用Sum[R]-Sum[S-1]來表示[L,R]區間的Sum值

就可以找到第K大的數了。


還有,既然一開始是一顆空樹,就不需要建立一整棵顆空樹了,直接用0表示空樹,省去了開始的一棵樹的空間,

這樣的話需要的空間就是n*log2n

所以主席樹的初始化變得跟SBT一樣簡潔:

L[0]=R[0]=Sum[0]=T[0]=TP=0;


代碼:

主席樹代碼:(811MS  27552KB)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#define maxn 100007
#define maxnn  1860007
using namespace std; 
//主席樹 
int L[maxnn],R[maxnn],Sum[maxnn],T[maxn],TP;//左右子樹,總和,樹根,指針 
void Add(int &rt,int l,int r,int x){//建立新樹
	++TP;L[TP]=L[rt];R[TP]=R[rt];Sum[TP]=Sum[rt]+1;rt=TP;//複製&新建
	if(l==r) return;
	int m=(l+r)>>1;
	if(x <= m) Add(L[rt],l,m,x);
	else       Add(R[rt],m+1,r,x);
}
int Search(int TL,int TR,int l,int r,int k){//區間查詢第k大
	if(l==r) return l;//返回第k大的下標
	int m=(l+r)>>1;
	if(Sum[L[TR]]-Sum[L[TL]]>=k) return Search(L[TL],L[TR],l,m,k);
	else return Search(R[TL],R[TR],m+1,r,k-Sum[L[TR]]+Sum[L[TL]]);
}
//常規
int n,m;
int value[maxn];
//離散化 
struct A{
	int x,id;
	bool operator<(const A&B)const{return x<B.x;}
}ID[maxn];
map<int,int> MP;
int Rank[maxn];
int main(void)
{
	int Test;scanf("%d",&Test);
	while(Test-->0){
		//讀取輸入+離散化 
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) {
			scanf("%d",&value[i]);
			ID[i].x=value[i];
			ID[i].id=i;
		}
		sort(ID+1,ID+n+1);MP.clear();
		for(int i=1;i<=n;++i){
			Rank[i]=ID[i].x;
			MP[ID[i].x]=i;
		}
		//初始化主席樹 
		L[0]=R[0]=Sum[0]=T[0]=TP=0;
		//建主席樹 
		for(int i=1;i<=n;++i) Add(T[i]=T[i-1],1,n,MP[value[i]]);
		//開始計算 
		for(int i=0;i<m;++i){
			int s,t,k;scanf("%d%d%d",&s,&t,&k);
			printf("%d\n",Rank[Search(T[s-1],T[t],1,n,k)]); 
		}
	}
return 0;
}



發佈了96 篇原創文章 · 獲贊 375 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章