主席树

原谅博主太蒟。。。实在讲不明白
http://www.cnblogs.com/zyf0163/p/4749042.html(讲得很好O(∩_∩)O~)

在粘模板之前,先进行一下简单的解释:
我们先 sort(a+1,a+1+n,cmp); 这是为什么呢~
其实,我们在构建线段树时,按照排序进行添加,
rank[i]是i元素在原始队列里的位置,添加第i个,就在rank[i]的位置上++,
实际上这样就(在不知不觉中)实现了离散化,

再看一下这个

void insert(int num,int &now,int l,int r)

这里的num 就是该元素在原序列中的位置,now是它在线段树中的位置,
l是左端点,r是右端点,
在这里 now 加了&(取地址符)这也就是说,在进行递归构建线段树的过程中,
now传递的元素会改变,所以就不用特意去维护了

这里写代码片
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=100010;
struct node{
    int rank,v;
};
node a[N];
struct node1{
    int l,r,sum;
};
node1 tree[N*20];
int n,m,rank[N],tot=0,root[N],u,v,z;


void insert(int num,int &now,int l,int r)  //构建线段树,&取地址符 
{    
    tree[++tot]=tree[now];
    now=tot;
    tree[now].sum++;   //sum记录的是:相对于这个节点,有多少个比ta大的数 
    if (l==r) return;
    int mid=(l+r)>>1;
    if (num<=mid) insert(num,tree[now].l,l,mid);  //该元素在线段树的左儿子中 
    else  insert(num,tree[now].r,mid+1,r); //该元素在线段树的右儿子中 
}

int cmp(const node &a,const node &b)
{
    return a.v<b.v;  //从小到大排序 
}

int q(int i,int j,int k,int l,int r)  //[i,j]是询问区间 
{
    if (l==r) return l;
    int ans=tree[tree[j].l].sum-tree[tree[i].l].sum;
    int mid=(l+r)>>1;
    if (k<=ans) q(tree[i].l,tree[j].l,k,l,mid);  
    else q(tree[i].r,tree[j].r,k-ans,mid+1,r);  //k-ans!!! 
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i].v);
        a[i].rank=i;
    }
    sort(a+1,a+1+n,cmp);  //排序后相当于建好了一颗小根堆 
    for (int i=1;i<=n;i++)
        rank[a[i].rank]=i; //rank[i] 原序列中第i个排第几
    for (int i=1;i<=n;i++)
    {
        root[i]=root[i-1];  //记录一下根 
        insert(rank[i],root[i],1,n); 
    } 
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&z);
        printf("%d
",a[q(root[u-1],root[v],z,1,n)].v);
    }
    return 0;
} 
原文地址:https://www.cnblogs.com/wutongtong3117/p/7673609.html