POJ - 2104
主席樹和值域線段樹存的值都是一樣的,但是主席樹是n棵線段樹,第i棵線段樹存儲的是i和i之前的所有信息(和前綴和類似),所以在求有關區間L~R的時候就可以用第R棵線段樹減去第L-1棵線段樹對應節點的值就得到了L~R之間的線段樹。
類似值域線段樹,沒次插入一個數的時候最多修改一條樹鏈,所以我們在給每個節點都建議可線段樹的時候,它和前一棵線段樹的差別也只有一條鏈,所以當我們在建第i棵線段樹的時候每當遇到一個節點,只需把第i-1棵線段樹的相應節點的信息copy過來然後再根據需要修改的鏈來改變該節點某一個子節點的指向,而另一個就和第i-1棵線段樹共用一個節點,這樣就大大的節省了空間,每新建一棵線段樹我們需要新開的節點數最多是log(數據範圍)(還可以離散優化)。
區間的第k小就是根據主席樹得到這個區間的線段樹之後再查詢在這可樹上查詢,返回結果是要求的值,離散後返回的值是離散的下標。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=5e6+10;
const int inf=0x3f3f3f3f;
int n,m;
struct AC
{
struct zp
{
int l,r,sum;
} tree[maxn];
int cont;
void init()
{
cont=1;
tree[0].l=tree[0].r=tree[0].sum=0;//第0棵線段樹每一個節點的值都是0所以可以用一個點來表示使其不斷循環形成第0棵線段樹
}
/*
每新建一棵線段樹相比於前面的那棵線段樹更改的只會是一條樹鏈,其他的節點都可以共用
新建節點,並且將前一棵線段樹的節點值複製過來,相應的數量++,並更改上一個節點的指向,這裏用的是引用變量
*/
void update(int pre,int &k,int l,int r,int num,int val)//
{
//
tree[cont]=tree[pre],tree[cont].sum+=val,k=cont++;
if(l==r) return ;//遇到根節點返回
int mid=(l+r)>>1;//繼續往下遞歸
if(num<=mid)
update(tree[pre].l,tree[k].l,l,mid,num,val);
else
update(tree[pre].r,tree[k].r,mid+1,r,num,val);
}
/*
查詢ql+1~qr區間第k小,用第qr可線段樹和第ql棵線段樹相應節點sum值作差的到一棵新的線段樹
這棵線段樹代表的就是這個區間的線段樹,在上面查找第k大就好
*/
int query(int k,int l,int r,int ql,int qr)//返回的是離散後的數組下標
{
if(l==r) return l;
int sum=tree[tree[qr].l].sum-tree[tree[ql].l].sum;//求出左節點的值大小
int mid=(l+r)>>1;
if(sum>=k)//在左節點
return query(k,l,mid,tree[ql].l,tree[qr].l);
else//在右節點
return query(k-sum,mid+1,r,tree[ql].r,tree[qr].r);
}
} ac;
int x[maxn],root[maxn],Hash[maxn];
int get_hash(int x)//值太大的話就要選擇離散
{
return lower_bound(Hash+1,Hash+1+n,x)-Hash;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
ac.init();
for(int i=1; i<=n; i++)
scanf("%d",&x[i]),Hash[i]=x[i];
sort(Hash+1,Hash+n+1);//離散
for(int i=1; i<=n; i++)//每一個節點新建一棵線段樹
ac.update(root[i-1],root[i],1,n,get_hash(x[i]),1);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
printf("%d\n",Hash[ac.query(c,1,n,root[a-1],root[b])]);
}
}
}