1. 程式人生 > >求區間第k小(主席樹)

求區間第k小(主席樹)

區間第k小

題目描述

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

輸入格式

第一行包含兩個正整數NNMM,分別表示序列的長度和查詢的個數。
第二行包含NN個正整數,表示這個序列各項的數字。
接下來MM行每行包含三個整數 l,r,kl,r,k,表示查詢區間[l,r][l,r]內的第kk小值。

輸出格式

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

輸入樣例

55 55
2595725957 64056405 1577015770 2628726287 2646526465

465
22 22 11
33 44 11
44 55 11
11 22 22
44 44 11

輸出樣例

64056405
1577015770
2628726287
2595725957
2628726287

說明
資料範圍:

對於20%的資料滿足:1N,M101 \leq N, M \leq 10
對於50%的資料滿足:1N,M1031 \leq N, M \leq 10^3
對於80%的資料滿足:1N,M1051 \leq N, M \leq 10^5
對於100%的資料滿足:1N,

M21051 \leq N, M \leq 2\cdot 10^5
對於數列中的所有數aia_i,均滿足109ai109-{10}^9 \leq a_i \leq {10}^9

樣例資料說明:

N=5N=5,數列長度為55,數列從第一項開始依次為[25957,6405,15770,26287,26465][25957, 6405, 15770, 26287, 26465 ]
第一次查詢為[2,2][2,2]區間內的第一小值,即為64056405
第二次查詢為[3,4][3,4]

4]區間內的第一小值,即為1577015770
第三次查詢為[4,5][4,5]區間內的第一小值,即為2628726287
第四次查詢為[1,2][1,2]區間內的第二小值,即為2595725957
第五次查詢為[4,4][4,4]區間內的第一小值,即為2628726287

先簡化問題,如果求的是多次詢問同一段區間的第kk小,我們可以怎麼做?
用權值線段樹對離散化後的數列進行統計,並在線段樹上二分,若小於等於xx的數的個數>=k>=k,則答案一定<=x<=x,否則答案一定>x>x
在權值線段樹上二分就可以了。

現在我們要求任意一段區間的第kk小,相當於要求出任意一段的權值線段樹。有因為是靜態的,很容易想到字首和。我們對每一個字首都開一棵建立在離散化陣列上的權值線段樹,若詢問ll ~ rr區間內的第kk小,只要將第rr棵線段樹上的每個節點的數值-ll棵線段樹上的每個節點的數值就能生成目標線段樹。

但是我們會發現一個問題,那就是將一整棵線段樹賦值的時間與空間複雜度都是無法承受的。因此,主席樹最最精妙的一點就是對於每一個時刻都保留前一個時刻樹的整體,而只修改一條鏈資料。這樣,預處理時間和空間複雜度都降到了O(logn)O(logn)

程式碼

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 200005;
const int maxt = 7500000;
int n;
int a[maxn];
int read()
{
	char ch = getchar(); bool f = 1;
	while(ch < '0' || ch > '9') f &= ch != '-' , ch = getchar();
	int res = 0;
	while(ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + (ch ^ 48) , ch = getchar();
	return f ? res : -res;
}
void write(int x)
{
	if(x < 0) x = -x , putchar('-');
	int len = 0 , res[15];
	for(;x;res[++len] = x % 10 , x /= 10);
	for(int i = len;i >= 1;i--) putchar(res[i] + 48);
	if(!len) putchar('0');
}
struct inm{int ls , rs , cnt;};
struct CT
{
	private:
		int m;
		int b[maxn];
		int cnt;
		inm t[maxt]; int rt[maxn];
		void build(int &p , int l , int r)
		{
			p = ++cnt;
			if(l == r) return;
			int mid = l + r >> 1;
			build(t[p].ls , l , mid);
			build(t[p].rs , mid + 1 , r);
		}
		void update(int &p , int pre , int l , int r , int x)
		{
			p = ++cnt;
			t[p] = t[pre];
			t[p].cnt++;
			if(l == r) return;
			int mid = l + r >> 1;
			if(x <= mid) update(t[p].ls , t[pre].ls , l , mid , x);
			else update(t[p].rs , t[pre].rs , mid + 1 , r , x);
		}
		int kth(int p , int pre , int l , int r , int k)
		{
			if(l == r) return l;
			int mid = l + r >> 1 , num = t[t[p].ls].cnt - t[t[pre].ls].cnt;
			if(num >= k) return kth(t[p].ls , t[pre].ls , l , mid , k);
			else return kth(t[p].rs , t[pre].rs , mid + 1 , r , k - num);
		}
	public:
		void clear()
		{
			cnt = 0;
			for(int i = 1;i <= n;i++) rt[i] = 0;
		}
		void init()
		{
			for(int i = 1;i <= n;i++) b[i] = a[i];
			sort(b + 1 , b + n + 1);
			m = unique(b + 1 , b + n + 1) - b - 1;
			build(rt[0] , 1 , m);
			for(int i = 1;i <= n;i++)
			{
				int loc = lower_bound(b + 1 , b + m + 1 , a[i]) - b;
				update(rt[i] , rt[i - 1] , 1 , m , loc);
			}
		}
		int query(int l , int r , int k){return b[kth(rt[r] , rt[l - 1] , 1 , m , k)];}
}ct;
int main()
{
	n = read();
	int q = read();
	for(int i = 1;i <= n;i++) a[i] = read();
	ct.clear() , ct.init();
	while(q--)
	{
		int l = read() , r = read() , k = read();
		write(ct.query(l , r , k));
		putchar('\n');
	}
	return 0;
}