[POJ2104]K-th Number(静态主席树)

题目描述

传送门

题解

静态主席树维护。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int max_n=1e5+5;

int val[max_n],loc[max_n],num[max_n],root[max_n];
struct hp{
    int l,r,val;
}ptree[max_n*20];
int n,m,x,y,z,sz,ans;

inline int cmp(int a,int b){
    return val[a]<val[b];
}

inline void build(int &now,int l,int r,int x){
    int mid=(l+r)>>1;
    ptree[++sz]=ptree[now]; now=sz;
    ptree[now].val++;
    if (l==r) return;
    if (x<=mid)
      build(ptree[now].l,l,mid,x);
    else
      build(ptree[now].r,mid+1,r,x);
}

inline int query(int i,int j,int l,int r,int k){
    if (l==r) return l;
    int mid=(l+r)>>1;
    int t=ptree[ptree[j].l].val-ptree[ptree[i].l].val;
    if (t>=k) return query(ptree[i].l,ptree[j].l,l,mid,k);
    else return query(ptree[i].r,ptree[j].r,mid+1,r,k-t);
}

int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i){
        scanf("%d",&val[i]);
        loc[i]=i;
    }

    sort(loc+1,loc+n+1,cmp);
    for (int i=1;i<=n;++i)
      num[loc[i]]=i;

    sz=0; root[0]=0;
    for (int i=1;i<=n;++i){
        root[i]=root[i-1];
        build(root[i],1,n,num[i]);
    }

    for (int i=1;i<=m;++i){
        scanf("%d%d%d",&x,&y,&z);
        ans=query(root[x-1],root[y],1,n,z);
        printf("%d\n",val[loc[ans]]);
    }
}

总结

抄别人的模板,但是还是想了很久很久,旁边的hxy神犇(一眼秒懂)和ATP神犇(一眼秒懂)看着我说不出话来,╮(╯▽╰)╭没办法只能怪自己太煞笔。
①普通的线段树是不可减的,但是我们分别维护1-2,1-3,1-4……1-n的权值线段树就可以转化成相减的线段树(权值线段树的意思就是排序后按照下标维护,也就是说将数据离散化之后原始数据变得毫无意义,我们需要的是这个数据在原序列中位于第几小的位置,以此来建树)。
②由于维护1~i+1这个区间的线段树时只比1~i这个区间的线段树多出来了一个数,所以只会在前一个线段树上影响一条链的值(从根到叶子)。
③每一次修改只是存一条链,即空间复杂度为O(nlogn),在前一个的基础上进行修改,并且设指针记录。
④由于从1~n棵树的形态都是相同的(空结点也算),可以通过一层一层的指针找出这棵树的完整形态。
⑤给初学者的忠告:这真的是非常简单易懂的代码模板,如果不懂的话就调试一下,注意递归调用时&的作用。当把watch的所有数据列出来之后,原理一目了然。

你可能感兴趣的:(poj,静态主席树)