带插入区间K小值 的题解
目前有的做法是(默认元素个数、操作数、值域同阶):
- 重量平衡树/替罪羊树+线段树合并,时间
O(n\log^2 n) ,空间O(n\log^2 n) ,常数较大。 - 定期分裂块状链表,时间
O(n\sqrt{n}) ,空间O(n\sqrt n) ,常数较小。
其他什么二分+树套树、操作分块或各种分块、分块/平衡树/线段树/树状数组互相套的做法就不仔细列了,要么常数被第二个做法吊打要么理论复杂度被第一个做法吊打。
第一种做法的空间常数较为玄学导致数组大小有些难开(写指针式线段树时空常数双炸),原因是重量平衡树旋转时重构子树的
实际上我们采用这种策略:每
有人可能要问:替罪羊树的均摊是不是不对?
最终我们得到了时间 可无奈其空间常数也实在太小了,拼尽全力无法战胜。
下面给出个参考代码,挺短的,最大的点 1.94s,63.42MB,或许可以继续调参做到更优。
#include<bits/stdc++.h>
using namespace std;
const double alpha=0.955;
const int MAXN=35e3,MAXM=14e4,MAXV=7e4,LOGM=ceil(log(MAXM)/log(1.0/alpha)+1);
const int SIZE=6e6,LIM=5e6;
int Read()
{
int res=0;char c;
while(!isdigit(c=getchar()));
while(isdigit(c)) res=res*10+c-'0',c=getchar();
return res;
}
int GetOP()
{
char c;
while(!isalpha(c=getchar()));
if(c=='Q') return 1;
if(c=='M') return 2;
return 3;
}
void Print(int x)
{
if(x<10) {putchar('0'+x);return;}
int y=x/10;
Print(y);
putchar('0'+x-y*10);
}
int sum[SIZE+5],lson[SIZE+5],rson[SIZE+5],tot;
int New(int x) {sum[++tot]=x; return tot;}
int Plus(int now,int L,int R,int x,int v)
{
int res=New(sum[now]+v);
if(L==R) {lson[res]=rson[res]=0; return res;}
int mid=(L+R)>>1;
if(x<=mid) lson[res]=Plus(lson[now],L,mid,x,v),rson[res]=rson[now];
else lson[res]=lson[now],rson[res]=Plus(rson[now],mid+1,R,x,v);
return res;
}
int Merge(int a,int b)
{
if(!a || !b) return a^b;
int res=New(sum[a]+sum[b]);
lson[res]=Merge(lson[a],lson[b]);
rson[res]=Merge(rson[a],rson[b]);
return res;
}
int n,q,root,A[MAXM+5],Size[MAXM+5],Lson[MAXM+5],Rson[MAXM+5],rt[MAXM+5],val[MAXM+5],m;
int Q[4*LOGM+5],Tail;
void GetQ(int now,int L,int R)
{
if(L==1 && R==Size[now]) {Q[++Tail]=rt[now];return;}
if(L<=Size[Lson[now]]+1 && Size[Lson[now]]+1<=R) Q[++Tail]=val[now];
if(L<=Size[Lson[now]]) GetQ(Lson[now],L,min(R,Size[Lson[now]]));
if(Size[Lson[now]]+1<R) GetQ(Rson[now],max(1,L-Size[Lson[now]]-1),R-Size[Lson[now]]-1);
}
int Search(int L,int R,int x)
{
if(L==R) return L;
int Lsize=0,mid=(L+R)>>1;
for(int i=1;i<=Tail;i++) Lsize+=sum[lson[Q[i]]];
if(x<=Lsize) {for(int i=1;i<=Tail;i++) Q[i]=lson[Q[i]]; return Search(L,mid,x);}
for(int i=1;i<=Tail;i++) Q[i]=rson[Q[i]]; return Search(mid+1,R,x-Lsize);
}
int Query(int L,int R,int x) {Tail=0,GetQ(root,L,R); return Search(0,MAXV,x);}
int Modify(int now,int x,int v)
{
if(Size[Lson[now]]+1==x)
{
int res=A[now];
val[now]=Plus(val[now],0,MAXV,A[now],-1),rt[now]=Plus(rt[now],0,MAXV,A[now],-1);
A[now]=v;
val[now]=Plus(val[now],0,MAXV,A[now],1),rt[now]=Plus(rt[now],0,MAXV,A[now],1);
return res;
}
int res;
if(x<=Size[Lson[now]]) res=Modify(Lson[now],x,v);
else res=Modify(Rson[now],x-Size[Lson[now]]-1,v);
rt[now]=Plus(rt[now],0,MAXV,res,-1),rt[now]=Plus(rt[now],0,MAXV,v,1);
return res;
}
int tmp[MAXM+5];
void GetTmp(int now) {if(now) GetTmp(Lson[now]),tmp[++Tail]=now,GetTmp(Rson[now]);}
int Build(int L,int R)
{
if(L>R) return 0;
int mid=(L+R)>>1,now=tmp[mid];
Lson[now]=Build(L,mid-1),Rson[tmp[mid]]=Build(mid+1,R);
rt[now]=Merge(Merge(rt[Lson[now]],rt[Rson[now]]),val[now]=Plus(0,0,MAXV,A[now],1));
Size[now]=Size[Lson[now]]+1+Size[Rson[now]];
return now;
}
void Rebuild(int &now) {Tail=0,GetTmp(now),now=Build(1,Tail);}
void Insert(int &now,int x,int v)
{
if(!now) {A[now=++m]=v,rt[now]=val[now]=Plus(0,0,MAXV,v,1),Size[now]=1;return;}
++Size[now];
rt[now]=Plus(rt[now],0,MAXV,v,1);
if(x<=Size[Lson[now]]+1) Insert(Lson[now],x,v);
else Insert(Rson[now],x-Size[Lson[now]]-1,v);
if(alpha*Size[now]<max(Size[Lson[now]],Size[Rson[now]])) Rebuild(now);
}
int main()
{
n=m=Read();
for(int i=1;i<=n;i++) A[i]=Read(),tmp[i]=i;
root=Build(1,n);
q=Read();
int cnt=0;
for(int opt,x,y,lst=0;q--;)
{
opt=GetOP(),x=Read()^lst,y=Read()^lst;
if(opt==1) Print(lst=Query(x,y,Read()^lst)),putchar('\n');
else if(opt==2) Modify(root,x,y);
else Insert(root,x,y);
if(tot>LIM) tot=0,Rebuild(root);
}
return 0;
}
UT 给了个常数更小的做法:外层用树状数组维护值域,内层用平衡树维护下标,需要比较两个元素位置的前后关系,用动态标号法(参考可持久化并查集和后缀平衡树)。