=====树套树=====
====线段树套线段树====
虽然称为线段树套线段树,其实大多是通过树状数组套主席树来实现的(没错就是[带修主席树](http://hotpot.clatisus.com/LOTK%ef%bc%88lxh%ef%bc%89/主席树)),用以实现查询k在区间内的排名,查询区间内排名为k的数,修改某一位的值,查询某一位的前驱后继等。
====线段树套平衡树====
如果说,要解决区间上的问题,如最大值,区间修改等,我们选择的显然是线段树。
但是线段树不能查询第k大,不能查询一个数在区间的排名,自然也不能查询前驱和后继,这些只能交给平衡树来解决。
而涉及到区间翻转时,显然也是平衡树的范畴,这种时候我们就需要将两种树形结合起来解决这类更复杂的问题。
特别是涉及到区间翻转,分析后我们不难发现,在前驱后继,k在区间内的排名,排名为k的数的查询等操作,线段树套平衡树并不会比线段树套线段树更优。(查找排名为k的数由于二分甚至会多个$log$)
下面给出一道模板题[[https://www.luogu.com.cn/problem/P3380|洛谷P3380 【模板】二逼平衡树(树套树)]]
并没有找到带区间翻转和区间查询的题,读者如果有兴趣可以出一道(
下面给出代码(这个题卡常。。。甚至吃评测机状态,之前90分的代码突然变70分。。。然后A了)
平衡树的代码部分我就不进行讲解,用的是[平衡树](http://hotpot.clatisus.com/LOTK%ef%bc%88lxh%ef%bc%89/平衡树)的板子
===insert/delete===
插入操作并没有什么难度,就是在插入的这个点的这条链上的平衡树都插入这个点,删除同理
void insert(int x,int p,int w){
ins(tr[x].root,w);
if(tr[x].l==tr[x].r)return;
int mid=(tr[x].l+tr[x].r)>>1;
if(p<=mid)insert(x<<1,p,w);
else insert(x<<1|1,p,w);
}
void Delete(int x,int p,int w){
del(tr[x].root,w);
if(tr[x].l==tr[x].r)return;
int mid=(tr[x].l+tr[x].r)>>1;
if(p<=mid)Delete(x<<1,p,w);
else Delete(x<<1|1,p,w);
}
===query===
关键的一步操作,我们将所要用到的平衡树的根放在一个vector里方便使用
vector vec;
void query(int x,int l,int r){
if(tr[x].l>r||tr[x].r
===pre/suc===
对于求前驱后继,我们就在每棵平衡树中求然后取最大最小即可。
int Pre(int k){
int ans=-INF;
for(auto x:vec)ans=max(ans,pre(k,x));
vec.clear();
return ans;
}
int Suc(int k){
int ans=INF;
for(auto x:vec)ans=min(ans,suc(k,x));
vec.clear();
return ans;
}
===rank===
每棵平衡树求rank相加
int Rank(int k){
int ans=0;
for(auto x:vec)ans+=get_rank(k,x);
vec.clear();
return ans;
}
===kth===
这步操作比较复杂,也是复杂度最大的部分,因为我们并没有直接得到k大的方法,于是只能进行二分,然后用Rank来确定其排名,确定排名我们还需得到排名的上下界,否则易错判。
更重要的一点,要对一开始可能输入的值排序处理方便二分,不然二分增加的这个$log$偏大导致超时
int calc1(int k){
int ans=0;
for(auto x:vec)ans+=get_rank(k,x);
return ans;
}
int calc2(int k){
int ans=0;
for(auto x:vec)ans+=get_rank2(k,x);
return ans;
}
int b[maxn],bcnt;
int get_kth(int k){
int L=1,R=bcnt;
while(L<=R){
int mid=(L+R)>>1;
int l=calc1(b[mid])+1,r=calc2(b[mid]);
if(l<=k&&k<=r){vec.clear();return b[mid];}
if(l>k)R=mid-1;
if(r
===完整代码===
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void write(int x){
if(x<0){putchar('-');write(-x);}
else {
if(x/10)write(x/10);
putchar(x%10+'0');
}
}
#define ull unsigned long long
const int maxn=2000005;
ull hval[maxn];
int cnt;
ull Rand(){
static ull r=1;
return (r*=998244353)%2147483647;
}
int num[maxn],sum[maxn],son[maxn][2],val[maxn];
int newnode(int x){
++cnt;num[cnt]=sum[cnt]=1;
val[cnt]=x;hval[cnt]=Rand();
return cnt;
}
void pushup(int x){
sum[x]=sum[son[x][0]]+sum[son[x][1]]+num[x];
}
void Rot(int &p,int k){
int x=son[p][k];
son[p][k]=son[x][k^1];
son[x][k^1]=p;
pushup(p);pushup(x);
p=x;
}
void ins(int &p,int x){
if(!p){p=newnode(x);return;}
++sum[p];
if(val[p]==x){++num[p];return;}
ins(son[p][x>val[p]],x);
if(hval[p]>hval[son[p][x>val[p]]])Rot(p,x>val[p]);
}
void del(int &p,int x){
--sum[p];
if(val[p]==x){
if(num[p]>1)--num[p];
else if(!son[p][0]||!son[p][1])p=son[p][0]+son[p][1];
else {Rot(p,hval[son[p][0]]>hval[son[p][1]]);del(p,x);}
}
else del(son[p][x>val[p]],x);
}
int get_rank(int x,int root){
int p=root,ans=0;
while(p){
if(val[p]==x){return ans+sum[son[p][0]];}
else if(x>val[p]){ans+=sum[son[p][0]]+num[p];p=son[p][1];}
else p=son[p][0];
}
return ans;
}
int get_rank2(int x,int root){
int p=root,ans=0;
while(p){
if(val[p]==x){return ans+sum[son[p][0]]+num[p];}
else if(x>val[p]){ans+=sum[son[p][0]]+num[p];p=son[p][1];}
else p=son[p][0];
}
return ans;
}
const int INF=2147483647;
int pre(int x,int root){
int p=root,ans=-INF;
while(p){
if(val[p]>=x)p=son[p][0];
else {ans=val[p];p=son[p][1];}
}
return ans;
}
int suc(int x,int root){
int p=root,ans=INF;
while(p){
if(val[p]<=x)p=son[p][1];
else {ans=val[p];p=son[p][0];}
}
return ans;
}
struct tree{
int l,r,root;
} tr[maxn];
int n,m,w[maxn];
void build(int x,int l,int r){
tr[x].l=l;tr[x].r=r;
if(l==r)return;
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
}
void insert(int x,int p,int w){
ins(tr[x].root,w);
if(tr[x].l==tr[x].r)return;
int mid=(tr[x].l+tr[x].r)>>1;
if(p<=mid)insert(x<<1,p,w);
else insert(x<<1|1,p,w);
}
vector vec;
void query(int x,int l,int r){
if(tr[x].l>r||tr[x].r>1;
int l=calc1(b[mid])+1,r=calc2(b[mid]);
if(l<=k&&k<=r){vec.clear();return b[mid];}
if(l>k)R=mid-1;
if(r>1;
if(p<=mid)Delete(x<<1,p,w);
else Delete(x<<1|1,p,w);
}
int Pre(int k){
int ans=-INF;
for(auto x:vec)ans=max(ans,pre(k,x));
vec.clear();
return ans;
}
int Suc(int k){
int ans=INF;
for(auto x:vec)ans=min(ans,suc(k,x));
vec.clear();
return ans;
}
struct Query{
int opt,l,r,k;
}q[maxn];
int main(){
n=read();m=read();
build(1,1,n);
for(int i=1;i<=n;++i){
w[i]=read();
insert(1,i,w[i]);
b[++bcnt]=w[i];
}
for(int i=1;i<=m;++i){
q[i].opt=read();
q[i].l=read();q[i].r=read();
if(q[i].opt!=3)q[i].k=read();
else b[++bcnt]=q[i].r;
}
sort(b+1,b+bcnt+1);
for(int i=1,opt,l,r,k;i<=m;++i){
opt=q[i].opt;l=q[i].l;r=q[i].r;
if(opt!=3)k=q[i].k;
if(opt==1){query(1,l,r);write(Rank(k)+1);putchar('\n');}
if(opt==2){query(1,l,r);write(get_kth(k));putchar('\n');}
if(opt==3){Delete(1,l,w[l]);w[l]=r;insert(1,l,w[l]);}
if(opt==4){query(1,l,r);write(Pre(k));putchar('\n');}
if(opt==5){query(1,l,r);write(Suc(k));putchar('\n');}
}
return 0;
}
====平衡树套线段树(替罪羊)====
一开始想到平衡树套线段树会感到很奇怪,毕竟大部分的平衡树树形是会改变的,这样的话在改变的过程中同时维护线段树就很难实现。
那么有没有这样一棵树,又能保持平衡,又能不轻易改变树形呢?
答案是有,替罪羊树完美保证了树形不轻易改变的性质,在重构的同时我们也重构我们的线段树,这样我们就能实现一种带插入的区间查询结构。能够实现插入删除以及区间k大(小)的查询。
而替罪羊树上的每个点,都保存着它子树的权值线段树,一旦重构,便自底向上进行线段树合并。
这里我们给出一道例题[[https://www.luogu.com.cn/problem/P4278|洛谷P4278 带插入区间K小值]]
这道题由于出题人魔改了数据,这种做法只能得20分,其余点TLE+MLE,不过题目来源bzoj好像木了,仅供练习使用吧。
===ins===
在插入替罪羊的同时我们插入线段树,同时用栈保留祖先关系(至于什么作用我们后面再说)
void ins(int x,int val){
int p=root;
while(p){
insert(rt[p],val,0,70000,1);
if(sum[son[p][0]]>=x){
sta[++top]=p;sn[top]=0;
p=son[p][0];
}
else {
x-=sum[son[p][0]]+1;
sta[++top]=p;sn[top]=1;
p=son[p][1];
}
}
p=newnode();w[p]=val;
insert(rt[p],w[p],0,70000,1);
son[sta[top]][sn[top]]=p;
for(int i=top;i>=1;--i)pushup(sta[i]);
}
===build===
这里也是替罪羊的rebuild部分,我们将重建的树所包含的点存好之后,对其重建并通过线段树合并整合信息。
void build(int &x,int l,int r){
if(l>r){x=0;return;}
if(l==r){
x=s[l];
insert(rt[x],w[x],0,70000,1);
return;
}
int mid=(l+r)>>1;x=s[mid];
build(son[x][0],l,mid-1);
build(son[x][1],mid+1,r);
pushup(x);
Merge(rt[x],rt[son[x][0]],rt[son[x][1]],0,70000);
insert(rt[x],w[x],0,70000,1);
}
===merge===
线段树合并
void Merge(int &x,int son0,int son1,int l,int r){
if(!x)x=newtr();
sum(x)=sum(son0)+sum(son1);
if(l==r)return;
int mid=(l+r)>>1;
if(sum(ls(son0))|sum(ls(son1)))Merge(ls(x),ls(son0),ls(son1),l,mid);
if(sum(rs(son0))|sum(rs(son1)))Merge(rs(x),rs(son0),rs(son1),mid+1,r);
}
===del===
在对树拍扁重建时对线段树上的点回收,否则消耗空间过于巨大
void del(int &x){
if(!x)return;
del(ls(x));del(rs(x));
Q.push(x);x=0;
}
其余操作没什么好说的,基本就是线段树和替罪羊树的操作,这里根据题涉条件讲两个操作
===关于区间提取===
在替罪羊树上提取我们所需的点的区间,如果是完全包含,则保存这点的权值线段树,单点包含则保存该点并继续递归子树。
void query(int x,int l,int r){
if(!x)return;
int L=sum[son[x][0]],R=sum[x];
if(l==1&&r==R){vectr.push_back(rt[x]);return;}
if(l<=L+1&&r>=L+1)vecp.push_back(x);
if(r<=L)query(son[x][0],l,r);
else if(l>L+1)query(son[x][1],l-L-1,r-L-1);
else {
if(l<=L)query(son[x][0],l,L);
if(R>L+1)query(son[x][1],1,r-L-1);
}
}
===关于得到答案===
这里我们采取在区间上二分的方式,L、R应时刻保持与区间一致(否则会影响单点的判断)
#define unt unsigned int
int solve(int l,int r,int k){
query(root,l,r);k--;
int L=0,R=70000;
unt n=vectr.size();
while(L>1;
int tot=0;
for(auto x:vectr)tot+=sum(ls(x));
for(auto x:vecp)if(L<=w[x]&&w[x]<=mid)++tot;
if(tot>k){
for(unt i=0;i
下面给出TLE+MLE完整代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void write(int x){
if(x<0){putchar('-');write(-x);}
else {
if(x/10)write(x/10);
putchar(x%10+'0');
}
}
const int maxn=20000005;
const int maxm=70005;
int n,root,sum[maxm];
int cnt;
int son[maxm][2];
//线段树
#define ls(x) tr[x].ls
#define rs(x) tr[x].rs
#define sum(x) tr[x].sum
struct tree{
int sum,ls,rs;
}tr[maxn];
int rt[maxm],cntt;
int w[maxm];
int s[maxm],tp;
queue Q;
void pushup(int nw){
sum[nw]=sum[son[nw][0]]+sum[son[nw][1]]+1;
}
int tr_get(){
if(Q.empty())return ++cntt;
int t=Q.front();Q.pop();
return t;
}
int newtr(){
int t=tr_get();
ls(t)=rs(t)=sum(t)=0;
return t;
}
void Merge(int &x,int son0,int son1,int l,int r){
if(!x)x=newtr();
sum(x)=sum(son0)+sum(son1);
if(l==r)return;
int mid=(l+r)>>1;
if(sum(ls(son0))|sum(ls(son1)))Merge(ls(x),ls(son0),ls(son1),l,mid);
if(sum(rs(son0))|sum(rs(son1)))Merge(rs(x),rs(son0),rs(son1),mid+1,r);
}
void insert(int &x,int val,int l,int r,int d){
if(!x)x=newtr();
sum(x)+=d;
if(l==r)return;
int mid=(l+r)>>1;
if(val<=mid)insert(ls(x),val,l,mid,d);
else insert(rs(x),val,mid+1,r,d);
}
void build(int &x,int l,int r){
if(l>r){x=0;return;}
if(l==r){
x=s[l];
insert(rt[x],w[x],0,70000,1);
return;
}
int mid=(l+r)>>1;x=s[mid];
build(son[x][0],l,mid-1);
build(son[x][1],mid+1,r);
pushup(x);
Merge(rt[x],rt[son[x][0]],rt[son[x][1]],0,70000);
insert(rt[x],w[x],0,70000,1);
}
void del(int &x){
if(!x)return;
del(ls(x));del(rs(x));
Q.push(x);x=0;
}
//替罪羊
int newnode(){
int t=++cnt;
sum[t]=1;
son[t][0]=son[t][1]=0;
return t;
}
void dfs(int x){
if(son[x][0])dfs(son[x][0]);
s[++tp]=x;del(rt[x]);
if(son[x][1])dfs(son[x][1]);
}
int sta[maxm],top;
bool sn[maxm];
void rebuild(int &nw){tp=0;dfs(nw);build(nw,1,tp);}
void get_reb(int &nw,int p,int d){
if(nw==p)return;
if(max(sum[son[nw][0]],sum[son[nw][1]])>sum[nw]*0.75)rebuild(nw);
else get_reb(son[nw][sn[d]],p,d+1);
}
int kth(int x){
int p=root;
while(true){
if(sum[son[p][0]]>=x)p=son[p][0];
else if(sum[son[p][0]]+1==x)return p;
else {x-=sum[son[p][0]]+1;p=son[p][1];}
}
}
void change(int x,int pv,int nv){
int p=root;
while(true){
insert(rt[p],pv,0,70000,-1);
insert(rt[p],nv,0,70000,1);
if(sum[son[p][0]]>=x)p=son[p][0];
else if(sum[son[p][0]]+1==x){w[p]=nv;return;}
else {x-=sum[son[p][0]]+1;p=son[p][1];}
}
}
void ins(int x,int val){
int p=root;
while(p){
insert(rt[p],val,0,70000,1);
if(sum[son[p][0]]>=x){
sta[++top]=p;sn[top]=0;
p=son[p][0];
}
else {
x-=sum[son[p][0]]+1;
sta[++top]=p;sn[top]=1;
p=son[p][1];
}
}
p=newnode();w[p]=val;
insert(rt[p],w[p],0,70000,1);
son[sta[top]][sn[top]]=p;
for(int i=top;i>=1;--i)pushup(sta[i]);
}
vector vectr,vecp;
void query(int x,int l,int r){
int L=sum[son[x][0]],R=sum[x];
if(l==1&&r==R){vectr.push_back(rt[x]);return;}
if(l<=L+1&&r>=L+1)vecp.push_back(x);
if(r<=L)query(son[x][0],l,r);
else if(l>L+1)query(son[x][1],l-L-1,r-L-1);
else {
if(l<=L)query(son[x][0],l,L);
if(R>L+1)query(son[x][1],1,r-L-1);
}
}
#define unt unsigned int
int solve(int l,int r,int k){
query(root,l,r);k--;
int L=0,R=70000;
while(L>1;
int tot=0;
for(auto x:vectr)tot+=sum(ls(x));
for(auto x:vecp)if(L<=w[x]&&w[x]<=mid)++tot;
if(tot>k){
for(auto &x:vectr)x=ls(x);
R=mid;
}
else {
for(auto &x:vectr)x=rs(x);
L=mid+1;k-=tot;
}
}
vecp.clear();vectr.clear();
return L;
}
int main(){
n=read();
for(int i=1;i<=n;++i){newnode();w[i]=read();s[i]=i;}
build(root,1,n);
int q=read();char s[4];
int lst=0;
for(int i=1,x,y,k;i<=q;++i){
scanf("%s",s);
x=read()^lst;y=read()^lst;
if(s[0]=='Q'){k=read()^lst;write(lst=solve(x,y,k));putchar('\n');}
if(s[0]=='M'){int p=kth(x);change(x,w[p],y);}
if(s[0]=='I'){ins(x-1,y);get_reb(root,cnt,1);top=0;}
}
return 0;
}
====平衡树套线段树(非旋treap)====
根据我们之前的思路,非旋treap的树形也不会轻易改变,因此我们可以采取非旋treap来实现,在split,merge之后用线段树合并来对所需要的信息进行维护
这是一个坑,待填