传送门
静态主席树维护。
#include
#include
#include
#include
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]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的所有数据列出来之后,原理一目了然。