主席树(可持久化线段树 )

给一个长为n的序列,m次询问,每次询问[l, r]内第k大的数是几。 n <= 100000, m <= 5000

比如有一个数组n个数据,那么这里记录的是从左往右,每个数据对应的线段树。最后的效果是两个数据的线段树对应值的差值。
而每个数据点(排序去重后所在坐标位置)对一个的线段树记录的是当前对应区间的个数sum(i) (i是区间标号)
举个栗子:
7 1
1 5 2 6 3 7 4
2 5 3
ans=5

update是将这个数据所在的数组位置(不是真值的数据大小排序,所以这里需要一个转换操作)向上走的每个位置sum都+1
用一个新数组记录原数组数据从小到大无重复的数据,然后遍历原数组的时就只要lowerbound查找此数据值对应的位置即可

查询区间[x,y]第k大数的操作为:
1.先抓取x-1与y这两点,两者对应线段树值dt=sum(y)-sum(x-1)作差
2.若k>dt,则说明对应值在右子树中;反之,若k<=dt,则说明在左子树中。(此环节用递归实现即可)

上代码:
#define mid (l+r)>>1
//建树,记录此节点对应左右节点的包含叶子的个数L(),R()
//第一个线段树只是个空树,后面update还会造m个线段树
inline void build(int l,int r){
int rt=++tot;
if(l<r){
L(rt)=build(l,mid);
R(rt)=build(mid+1,r);
}
return tot;
}
//pre是前一个线段树,现在造新的线段树
inline void update(int pre,l,r,x){
int rt=++tot;//因为有m个线段树,所以tot要不停的加
L(rt)=L(pre),R(rt)=R(pre),sum(rt)=sum(pre)+1;
if(l<r){
if(x<=mid) //去对应位置就好
update(rt,l,mid,x);
else
update(rt,mid+1,r,x);
}
return rt;
}
//查询,左右两子树转移
inline int query(int ql,qr,l,r,k){
if(l==r)return l;
int dt=sum(R(qr))-sum(L(ql));
if(dt>k)query(ql,qr,l,mid,k);
else query(ql,qr,mid+1,r,k-dt); //记得减掉dt
}

For(i,1,n)cin>>a(i),b(i)=a(i);
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-b-1; //unique返回的是第一个重复值的位置即m+1,所以还要-1
T(0)=build(1,m);
For(i,1,m){
a(i)=lower_bound(b+1,b+1+m,a(i))-b;
T(i)=update(T(i-1),1,m,a(i));
}
对[x,y]询问第k大的数
int p=query(T(x-1),T(y),1,m,z);
cout<<b(p);

猜你喜欢

转载自www.cnblogs.com/planche/p/9380208.html