不带修改的区间k-th number
这个众所周知了。可以用主席树实现。
具体的话其实就是开
n
棵权值线段树,动态开节点,由于每次增加一个点最多只会新建
我们在权值线段树上记录该范围数的出现次数,然后可以发现查询的两个区间端点的线段树信息可以相减,于是就可以线段树上二分了。时间复杂度
O(nlog2n)
。
可修改区间k-th number
%了一波Samjia2000
注意到像上面那样做的话,修改一个点就要把它后面所有点也一起修改,显然TLE。
上面的方法是不能兹瓷修改的。
那我们就需要树套树!
修改操作
我们用一个BIT,BIT上的每个点都是一棵线段树,位置为
x
的线段树记录了其前面
(请注意,主席树是真实的前缀和,而用树状数组则是伪前缀和)
修改操作,就在BIT上往后跳,跳到一个点就改一个点。
修改操作,就是BIT套线段树。
单次修改时间复杂度为
O(log22n)
查询操作
有了记录前缀信息的BIT,就能像BIT一样,往前跳。
起初我以为要把所有这些线段树合并到答案里,但这样做麻烦,而且时间很慢。
其实可以先记录好所需的线段树的编号,然后直接进入线段树,在线段树里再把记录的编号拿出来,两个端点相减二分。当在线段树里二分时,所记录的编号里面的所有点也要相应地走到左儿子或右儿子。
查询操作,就是线段树套BIT。
单次查询时间复杂度为
O(log22n)
总结
总的时间复杂度为
O((n+m)log22n)
空间复杂度为
O((n+m)log22n)
Code
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int N=10010,M=2000010;
int n,m,num,a[N],lv[N],rv[N],root[N],ls[M],rs[M],sum[M];
int lowbit(int x){return x&-x;}
void modify(int &v,int l,int r,int x,int p)//single point modify
{
if(!v) v=++num;
sum[v]+=p;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) modify(ls[v],l,mid,x,p);
else modify(rs[v],mid+1,r,x,p);
}
void add(int x,int y,int p)
{
for(;x<=n;x+=lowbit(x)) modify(root[x],1,n,y,p);
}
void turnL()
{
fo(i,1,rv[0]) rv[i]=ls[rv[i]];
fo(i,1,lv[0]) lv[i]=ls[lv[i]];
}
void turnR()
{
fo(i,1,rv[0]) rv[i]=rs[rv[i]];
fo(i,1,lv[0]) lv[i]=rs[lv[i]];
}
int query(int l,int r,int k)//k-th number
{
if(l==r) return l;
int mid=(l+r)>>1,t=0;
fo(i,1,rv[0]) t+=sum[rs[rv[i]]];
fo(i,1,lv[0]) t-=sum[rs[lv[i]]];
if(t>=k)
{
turnR();
return query(mid+1,r,k);
}
else
{
turnL();
return query(l,mid,k-t);
}
}
int main()
{
int tp,_,x,y,k;
scanf("%d",&n);
fo(i,1,n)
{
scanf("%d",&a[i]);
add(i,a[i],1);
}
scanf("%d",&_);
while(_--)
{
scanf("%d %d %d",&tp,&x,&y);
if(tp==0)
{
add(x,a[x],-1);
a[x]=y;
add(x,a[x],1);
}
else
{
scanf("%d",&k);
lv[0]=rv[0]=0;
for(int i=y;i;i-=lowbit(i)) rv[++rv[0]]=root[i];
for(int i=x-1;i;i-=lowbit(i)) lv[++lv[0]]=root[i];
//record position in BIT
printf("%d\n",query(1,n,k));
}
}
return 0;
}