可持久化是真的毒瘤,在网上找了很多资料才搞懂
(不过我觉得应该是我太蒻了)
struct Tree{
int left;
int right;
int size;
}node[5000001]; //用结构体存树节点的左孩子,右孩子和当前的权值
int tot_,a[5000001],b[5000001],N;
int Build_ (int L,int R){
int now=++tot_; //now为当前节点编号
int mid=(L+R)>>1; //获取区间中点
if (L!=R){ //边界
node[now].left=Build_ (L,mid); //向左建树
node[now].right=Build_ (mid+1,R); //向右建树
}
return now;
}
void MakePoint_ (int now,int L,int R,int V){ //求出树上每个节点的权值,V表示在数中插入的元素离散化后的值
++node[now].size; //因为V属于当前区间,所以对应区间的权值加一
if (R==L) return; //边界
int mid=(L+R)>>1;
if (V<=mid) MakePoint_ (node[now].left,node[k].left,L,mid,V); //如果V比mid小,那么V属于左子树的区间,向左继续更新权值
else MakePoint_ (node[now].right,node[k].right,mid+1,R,V); //否则向右更新权值
return;
}
int getnum_ (int k){ //用二分的思想获取原数组离散化后的值
int L=1,R=N;
while (L<=R){
int mid=(L+R)>>1;
if (b[mid]==k) return mid;
else if (b[mid]>k) R=mid-1;
else L=mid+1;
}
return 0;
}
int main (){
scanf ("%d",&N); //读取数列个数
for (int i=1;i<=N;++i) //读入数列,a数组为原序列,b数组为离散化的序列
scanf ("%d",&a[i]),b[i]=a[i];
sort (b+1,b+N+1); //将b数组排序,那么原a数组中的最小值对应1,第二小值对应2...
Build_ (1,N); //先把树的节点编号和左右孩子求出来
for (int i=1;i<=N;++i){
MakePoint_ (1,1,N,getnum_ (a[i])); //将元素一个一个插入树中并更新树上的权值
}
return 0;
}
int Query_ (int Tree,int L,int R,int K){ //查询函数 Tree表示当前节点的编号
//L,R表示区间边界
//K表示需要查询区间[L,R]的第K大元素
if (L==R) return L; //到达边界了,返回L
int mid=(L+R)>>1; //求中点的值
if (node[node[Tree].left].size>=K) return Query_ (node[Tree].left,L,mid,K); //如果当前节点左子树的值比K大则查询左子树
else return Query_ (node[Tree].right,mid+1,R,K-node[node[Tree].left].size); //否则查询右子树
}
#include <bits/stdc++.h>
#define RE register
#define IL inline
using namespace std;
struct Tree{
int left;
int right;
int size;
}node[5000001]; //用结构体存树节点的左孩子,右孩子和当前的权值
int tot_,a[5000001],b[5000001],N,M,Root_[5000001],Q;
IL int Build_ (int L,int R){
int now=++tot_; //now为当前节点编号
int mid=(L+R)>>1; //获取区间中点
if (L!=R){ //边界
node[now].left=Build_ (L,mid); //向左建树
node[now].right=Build_ (mid+1,R); //向右建树
}
return now;
}
IL int MakePoint (int before,int L,int R,int V){ //新增一条链,V表示在数中插入的元素离散化后的值
int now=++tot_;
node[now]=node[before]; //先将当前新增的节点连向前一棵树相同位置的左右子树,同时权值也与前一棵树相同位置相同
++node[now].size; //因为V属于当前区间,所以对应区间的权值加一
if (R!=L){//边界
int mid=(L+R)>>1;
if (V<=mid) node[now].left=MakePoint_ (node[before].left,L,mid,V); //如果V比mid小,那么V属于左子树的区间,新建一个左节点
//因为没有右子树没有任何更改,也就不需要建节点
//因为之前已经将当前位置的右子树修改为前一棵树当前位置的右子树
//所以不需要进行任何修改
else node[now].right=MakePoint_ (node[before].right,mid+1,R,V); //否则新建右子树
}
return now;
}
IL int Query_ (int TreeA,int TreeB,int L,int R,int K){ //查询区间[A+1,B]也就是查询第A棵树和第B棵树
//L,R表示区间边界
//K表示需要查询区间的第K大元素
if (L==R) return L; //到达边界了,返回L
int mid=(L+R)>>1; //求中点的值
int x=node[node[TreeB].left].size-node[node[TreeA].left].size; //计算两棵树相同位置的左子树的权值差
//即区间[A+1,B]中大小区间位于[L,R]的元素个数
if (x>=K) return Query_ (node[TreeA].left,node[TreeB].left,L,mid,K); //若这个个数比K大,则第K大在[L,mid]区间内
//也就是当前节点左子树维护的区间
else return Query_ (node[TreeA].right,node[TreeB].right,mid+1,R,K-x); //否则向右查询
}
IL int getnum_ (int k){ //用二分的思想获取原数组离散化后的值
int L=1,R=N;
while (L<=R){
int mid=(L+R)>>1;
if (b[mid]==k) return mid;
else if (b[mid]>k) R=mid-1;
else L=mid+1;
}
return o;
}
int main (){
scanf ("%d %d",&N,&Q); //读取数列个数和查询个数
for (RE int i=1;i<=N;++i) //读入数列,a数组为原序列,b数组为离散化的序列
scanf ("%d",&a[i]),b[i]=a[i];
sort (b+1,b+N+1); //将b数组排序,那么原a数组中的最小值对应1,第二小值对应2...
M=unique (b+1,b+N+1)-(b+1); //将数据去重,因为求第K大我们不需要重复的一样大小的元素
Root_[0]=++tot_; //0号线段树的根节点为1
Build_ (1,M); //建立最初始的树,也就是维护区间[0,0]的树
for (RE int i=1;i<=N;++i){
Root_[i]=MakePoint_ (Root_[i-1],1,M,getnum_ (a[i])); //将元素一个一个插入树中并更新树上的权值,
}
for(RE int i=1;i<=Q;++i){
int x,y,k; //查询[x,y]区间第k大
scanf ("%d %d %d",&x,&y,&k);
printf ("%d\n",b[Query_ (Root_[x-1],Root_ [y],1,M,k)]); //也就是查询第x-1和第y棵树
//查询函数返回的是序列第K大在原数列的大小位置
//因此要输出其在排序后的数组b中对应的值
}
return o;
}
#include <bits/stdc++.h>
#define LL long long
#define RE register
#define IL inline
using namespace std;
struct tree{ //这里和前面一样用结构体存树
LL right;
LL left;
LL size;
}node[1000001];
LL tot_,Root_[1000001],N,M,a[1000001];
IL LL Build_ (LL L,LL R){ //初始建树也差不多
LL now=++tot_;
if (L==R){
node[now].size=a[L]; //如果L=R即当前区间已经到最小,树也建到了叶节点
//将叶子节点的值更新为对应数组的值
}
else{
LL mid=(L+R)>>1;
node[now].left=Build_ (L,mid);
node[now].right=Build_ (mid+1,R);
}
return now;
}
IL LL MakePoint_ (LL Tree,LL L,LL R,LL I,LL K){ //建造一条链,思路 也是一样
LL now=++tot_;
node[now]=node[Tree];
if (L==R){
node[now].size=K; //到达边界则修改当前位置的节点
return now;
}
LL mid=(L+R)>>1;
if (I<=mid) node[now].left=MakePoint_ (node[Tree].left,L,mid,I,K); //如果需要修改的节点位置小于mid
//则新建左子树
else node[now].right=MakePoint_ (node[Tree].right,mid+1,R,I,K); //否则新建右子树
return now;
}
IL LL Qurey_ (LL Tree,LL L,LL R,LL K){
if (L==R) return node[Tree].size; //查询時只要返回对应位置的权值即可
LL mid=(L+R)>>1;
if (mid>=K) return Qurey_ (node[Tree].left,L,mid,K);
else return Qurey_ (node[Tree].right,mid+1,R,K);
}
int main (){
scanf ("%lld %lld",&N,&M);
for (RE int i=1;i<=N;++i)
scanf ("%lld",&a[i]);
Root_[0]=Build_ (1,N);
for (RE int i=1;i<=M;++i){
LL x,OP,y,z;
scanf ("%lld %lld %lld",&x,&OP,&y);
if (OP==1){
scanf ("%lld",&z),Root_[i]=MakePoint_ (Root_[x],1,N,y,z); //这一次是再第x的版本上新建链而不是i-1,同时注意新建版本
}
else{
Root_[i]=Root_[x]; //查询也算要建一次新版本
printf ("%lld\n",Qurey_ (Root_[i],1,N,y)); //查询
}
}
return 0;
}
原文:https://www.cnblogs.com/IQZ-HUCSr-TOE/p/12631101.html