树套树学习笔记
前言
其实是复习笔记,但是等于新学了。
前置知识
因为我比较懒惰,而且代码能力堪忧,所以我选择码量少的树状数组套主席树的写法。
所以前置知识有:树状数组,可持久化权值线段树(主席树)。
这俩我都写过,详见之前的学习笔记。
树套树
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
- 查询 \(k\) 在区间内的排名;
- 查询区间内排名为 \(k\) 的值;
- 修改某一位置上的数值;
- 查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(x\),且最大的数,若不存在输出
-2147483647
);- 查询 \(k\) 在区间内的后继(后继定义为严格大于 \(x\),且最小的数,若不存在输出
2147483647
)。对于一组元素,一个数的排名被定义为严格比它小的元素个数加一,而排名为 \(k\) 的数被定义为“将元素从小到大排序后排在第 \(k\) 位的元素值”。
是的这就是我们树套树干的事情,具体实现也很简单,只需要把两个数据结构的代码有脑地拼在一起即可。
对于每一个点开一个权值线段树,然后拿树状数组维护一下,修改的时候按照树状数组访问,然后按照权值线段树的修改方式修改节点。
具体而言,延续主席树的前缀和思想,不过这次我们先在树状数组上计算出来用到的节点,然后再进入主席树修改或者查询。
然后我们就可以查询第 \(k\) 小值和 \(k\) 在区间的排名,至于前驱后继就先查 \(k\) 的排名,再查 \(k+1/k-1\) 的值即可。
code:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e5+50;
const int inf=2147483647;
struct node{
int val,ls,rs;
}tr[N*100];
struct mdf{
int a,b,c,d;
}q[N];
int rt[N],tem[N],tmp[N],cnt,num,tot;
int n,m,lsh[2*N],len,a[N];
int find(int x){
return lower_bound(lsh+1,lsh+1+len,x)-lsh;
}
int lowbit(int x){
return x&-x;
}
void pushup(int u){
tr[u].val=tr[tr[u].ls].val+tr[tr[u].rs].val;
return ;
}
//正常修改操作
void modify(int &u,int l,int r,int x,int k){
if(!u) u=++tot;
if(l==r){
tr[u].val+=k;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) modify(tr[u].ls,l,mid,x,k);
else modify(tr[u].rs,mid+1,r,x,k);
pushup(u);
return ;
}
//找到要修改哪些节点
void add(int p,int k){
for(int i=p;i<=n;i+=lowbit(i)) modify(rt[i],1,len,a[p],k);
}
//正常查询排名为 k 的值
//tem对应原来主席树中的 ls[now],tmp对应 ls[pre]
//然后统计所有的和才是原来的 tr[ls[now]]-tr[ls[pre]]
//访问左右儿子的时候记得把 now,pre 换过去,也就是 tem,tmp
int query1(int l,int r,int k){
if(l==r){
return l;
}
int mid=(l+r)>>1,sum=0;
for(int i=1;i<=cnt;i++) sum+=tr[tr[tem[i]].ls].val;
for(int i=1;i<=num;i++) sum-=tr[tr[tmp[i]].ls].val;
if(k<=sum){
for(int i=1;i<=cnt;i++) tem[i]=tr[tem[i]].ls;
for(int i=1;i<=num;i++) tmp[i]=tr[tmp[i]].ls;
return query1(l,mid,k);
}else{
for(int i=1;i<=cnt;i++) tem[i]=tr[tem[i]].rs;
for(int i=1;i<=num;i++) tmp[i]=tr[tmp[i]].rs;
return query1(mid+1,r,k-sum);
}
}
//查询有哪些节点被访问了,并记录
int fnum(int l,int r,int k){
cnt=num=0;
for(int i=r;i;i-=lowbit(i)){
tem[++cnt]=rt[i];
}
for(int i=l-1;i;i-=lowbit(i)){
tmp[++num]=rt[i];
}
return query1(1,len,k);
}
//同上,因为是查询排名,所以递归右子树时加上左子树的和
int query2(int l,int r,int k){
if(l==r) return 0;
int mid=(l+r)>>1,sum=0;
if(k<=mid){
for(int i=1;i<=cnt;i++) tem[i]=tr[tem[i]].ls;
for(int i=1;i<=num;i++) tmp[i]=tr[tmp[i]].ls;
return query2(l,mid,k);
}else{
for(int i=1;i<=cnt;i++) sum+=tr[tr[tem[i]].ls].val,tem[i]=tr[tem[i]].rs;
for(int i=1;i<=num;i++) sum-=tr[tr[tmp[i]].ls].val,tmp[i]=tr[tmp[i]].rs;
return sum+query2(mid+1,r,k);
}
}
//同上
int frnk(int l,int r,int k){
cnt=num=0;
for(int i=r;i;i-=lowbit(i)){
tem[++cnt]=rt[i];
}
for(int i=l-1;i;i-=lowbit(i)){
tmp[++num]=rt[i];
}
return query2(1,len,k)+1;
}
//查前驱,如果是最小的返回 0
int fpri(int l,int r,int k){
int rk=frnk(l,r,k)-1;
if(rk==0) return 0;
return fnum(l,r,rk);
}
//查后继,如果是最大的返回 len+1
int fnxt(int l,int r,int k){
if(k==len) return len+1;
int rk=frnk(l,r,k+1);
if(rk==r-l+2) return len+1;
return fnum(l,r,rk);
}
int main(){
cin>>n>>m;
tot=cnt=num=0;
for(int i=1;i<=n;i++){
cin>>a[i];
lsh[++len]=a[i];
}
for(int i=1;i<=m;i++){
cin>>q[i].a>>q[i].b>>q[i].c;
if(q[i].a!=3) cin>>q[i].d;
else lsh[++len]=q[i].c;
if(q[i].a==4 || q[i].a==5) lsh[++len]=q[i].d;
}
//离散化
sort(lsh+1,lsh+1+len);
len=unique(lsh+1,lsh+1+len)-lsh-1;
for(int i=1;i<=n;i++){
a[i]=find(a[i]);
add(i,1);
}
//依据题意
lsh[0]=-inf;
lsh[len+1]=inf;
for(int i=1;i<=m;i++){
if(q[i].a==1){
q[i].d=find(q[i].d);
cout<<frnk(q[i].b,q[i].c,q[i].d)<<'\n';
}
if(q[i].a==2){
cout<<lsh[fnum(q[i].b,q[i].c,q[i].d)]<<'\n';
}
if(q[i].a==3){
add(q[i].b,-1);
a[q[i].b]=find(q[i].c);
add(q[i].b,1);
}
if(q[i].a==4){
q[i].d=find(q[i].d);
cout<<lsh[fpri(q[i].b,q[i].c,q[i].d)]<<'\n';
}
if(q[i].a==5){
q[i].d=find(q[i].d);
cout<<lsh[fnxt(q[i].b,q[i].c,q[i].d)]<<'\n';
}
}
return 0;
}