1. 程式人生 > >【POJ 2104】【主席樹模板題】K-th Number

【POJ 2104】【主席樹模板題】K-th Number

題意:

      靜態詢問區間第K大問題。給出一個數組,然後多次詢問某一區間第K大數是多少。

 

思路:

      典型的主席樹模板題。

      所以就大致講一下靜態主席樹的原理。

      先回顧一下權值線段樹,每個節點維護一個sum,表示陣列中有多少個點在這個節點所代表的左右區間內,這樣就可以求出全域性第k大問題。

      那麼主席樹就是建立多棵線段樹,第i個線段樹維護了 [1-i] 區間內的權值線段樹,那麼當求 [l,r] 這個區間內的第k大時,就只需要考慮 第l-1 和 第r 棵線段樹,將這兩個線段樹內相同節點的sum相減,就是[l,r]這個區間內的資訊了。

 

      然後因為第i棵線段樹和第i+1棵線段樹中有大量公用部分,所以每多建一棵線段樹,只需要多開logn個節點,實現了空間優化。

 

程式碼:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 1e5+100;

struct Tree{
	int lc,rc; //左右節點 t陣列 編號
	int l,r; //節點的左右端點
	int sum; 
}t[N*20];
int n,m,a[N],num,b[N],root[N],tot;

int build(int l,int r)
{
	int p = ++tot; // 新建一個節點,編號為p,代表當前區間[l,r]
	t[p].l = l, t[p].r = r, t[p].sum = 0;
	if(l == r) return p;
	int mid = (l+r)>>1;
	t[p].lc = build(l,mid);
	t[p].rc = build(mid+1,r);
	return p;
}

int insert(int now,int pos,int k)
{
	int p = ++tot;
	t[p] = t[now]; //建立副本
	if(t[p].l == t[p].r){
		t[p].sum += k; //在副本上修改
		return p;
	}
	int mid = (t[p].l+t[p].r)>>1;
	if(pos <= mid) t[p].lc = insert(t[p].lc,pos,k); //保留右兒子部分,把左兒子更新
	else t[p].rc = insert(t[p].rc,pos,k);
	t[p].sum = t[t[p].lc].sum + t[t[p].rc].sum;
	return p;
}

int ask(int lp,int rp,int k) //lp和rp所代表的區間是相同的,他們只不過是在不同狀態下的副本
{
	if(t[lp].l == t[lp].r) return t[lp].l; //找到答案
	int cnt = t[t[rp].lc].sum-t[t[lp].lc].sum; // 值在[l,mid]中的數有多少個
	if(cnt >= k) return ask(t[lp].lc,t[rp].lc,k);
	else return ask(t[lp].rc,t[rp].rc,k-cnt);
}

int main()
{
	num = tot = 0;
	scanf("%d%d",&n,&m);
	rep(i,1,n){
		scanf("%d",&a[i]);
		b[++num] = a[i];
	}
	sort(b+1,b+1+num); //離散化
	num = unique(b+1,b+1+num)-b-1;
	root[0] = build(1,num); //root[0]這顆樹是一棵空樹,關於離散化後的值域建樹
	rep(i,1,n)
	{
		int x = lower_bound(b+1,b+1+num,a[i])-b; //離散化後的值
		root[i] = insert(root[i-1],x,1); //值為x的數增加1個
	}
	rep(i,1,m)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		int ans = ask(root[x-1],root[y],z);
		printf("%d\n",b[ans]); //從離散化後的值變回原值
	}
	return 0;
	//root[i]:表示只考慮1-i這些數時候建樹的情況,這顆樹樹根的編號
}

/*
新建多個權值線段樹副本,記錄只考慮1-i個數時,每個數出現在各個區間的個數是多少,類似於建多棵權值線段樹
然後第i棵線段樹,參考第i-1棵線段樹,優化空間
空間為4*n*log(n)
*/