1. 程式人生 > >區間第k大(靜態)——主席樹

區間第k大(靜態)——主席樹

Description

給定一個長度為n的序列,m個詢問,每個詢問的形式為:L,r,k表示在[L,r]間中的第k大元素。

Input

第1行:2個數,n,m表示序列的長度和詢問的個數
第2行:n個數,表示n個數的大小
第3-m+2行:每行3個數,L,r,k表示詢問在[L,r]區間內第k小的元素

Output

對於每個詢問,輸出答案。

Sample Input

7 2 1 5 2 6 3 7 4 1 5 3 2 7 1

Sample Output

3 2

Hint

對於100%的資料,n<=100000, m<=100000,1<=L<=r<=n, 1<=k<=r-L+1

主席樹入門級題目,我們維護的是權值線段樹。

rt[x]表示第x個數當根的前x個線段樹的根,於是就有了字首和的性質,那麼我們查詢[l,r]的時候將rt[r]-rt[l-1]即可計算出來。

發現修改操作(單點修改)最多logn個點受影響,於是我們可以共用很多資訊,動態開點修改鏈上的。(這道題太水。。。感覺沒有突出主席樹的強大功能(雖然難了我也不會))。

值得注意的是主席樹的空間複雜度略高(貌似和樹套樹差不多啊。。。),開線段樹的時候記憶體記得開大一點,經過剛才的理性分析,似乎也是nlogn級別的空間複雜度?

#include<bits/stdc++.h>
using namespace std;
const int Maxn=100005;
struct SegMent{
	struct Node{
		int ls,rs,sum;
	}t[Maxn*8];
	int cnt,rt[Maxn];
	inline void modify(int val,int &x,int o,int l,int r){
		t[x=++cnt]=t[o];
		++t[x].sum;
		if(l==r)return ;
		int mid=l+r>>1;
		if(val<=mid)modify(val,t[x].ls,t[o].ls,l,mid);
		else modify(val,t[x].rs,t[o].rs,mid+1,r);
	}
	inline int query(int kth,int lt,int rt,int l,int r){
		int sum=t[t[rt].ls].sum-t[t[lt].ls].sum;
		int mid=l+r>>1;
		if(l==r)return mid;
		if(sum>=kth)return query(kth,t[lt].ls,t[rt].ls,l,mid);
		else return query(kth-sum,t[lt].rs,t[rt].rs,mid+1,r);
	}
}seg;
int main(){
	int n,m;scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		int a;scanf("%d",&a);
		seg.modify(a,seg.rt[i],seg.rt[i-1],0,1<<30);
	}
	for(int i=1;i<=m;++i){
		int l,r,k;scanf("%d%d%d",&l,&r,&k);
		printf("%d\n",seg.query(k,seg.rt[l-1],seg.rt[r],0,1<<30));
	}
	return 0;
}