模板,动态区间 k k k小:
给定一个长度为 n n n的数组, m m m次操作,每次操作只可能是下面其中之一:
(1)给定 l , r , k l,r,k l,r,k,求区间 [ l , r ] [l,r] [l,r]的 k k k小元素
(2)给定 x , y x,y x,y,把 a [ x ] a[x] a[x]改为 y y y
树状数组套主席树解法:
让我们回顾一下区间静态区间 k k k小的方法,利用主席树可以融合多颗线段树的性质,保存 i i i的前缀权值线段树,做差获得 [ l , r ] [l,r] [l,r]的权值线段树,直接在树上二分。显然, i i i的前缀权值线段树仅需要在 i − 1 i-1 i−1的前缀权值线段树上修改即可得到。总而言之,获得 [ l , r ] [l,r] [l,r]的权值线段树,再在树上二分就是本质思想。
不妨在上面的思路上继续思考,我们把 a [ x ] a[x] a[x]改成 y y y,那么只需要在 [ x , n ] [x,n] [x,n]的前缀上都修改一遍即可达成目的,可是这样的时间复杂度是 O ( n m l o g n ) O(nmlogn) O(nmlogn)的, m m m是修改次数,太慢
再进一步,我们能不能加速这个修改过程?由于是前缀线段树,并且存在区间求和,于是我们考虑树状数组,令树状数组每个点都是一个线段树,即主席树上的一个根,如果我们需要修改 x x x位置的,能将原来单次修改的时间复杂度由 O ( n l o g n ) O(nlogn) O(nlogn)优化为 O ( l o g 2 n ) O(log^2n) O(log2n)。那考虑如何获得差值权值线段树,在树状数组上,我们的区间求和策略是获得他们的前缀和做差,但由于树状数组存的是根,没有直接的前缀和,于是,每个根我们只存放一些链,也就是部分的线段树,当我们求得前缀和后,所有的链被合并在一起,成为一颗完整的线段树,这样就可以做差获得差值的权值线段树了
由于我们存放的是根,我们在合并后的线段树上二分,合并之后的线段树不好保存,不妨直接在部分树上分别二分,权值取所有对应点的总和,因此需要一个数组来保存他们的根,并且只有根满足 l o w b i t lowbit lowbit规则(因为只有根在树状数组上),所以预先处理处根的数组,然后树上二分时所有点同时向左/右子树移动。在线段树上二分的时间复杂度时 O ( l o g n ) O(logn) O(logn),同时二分 O ( l o g n ) O(logn) O(logn)条,于是单词查询时间复杂度是 O ( l o g 2 n ) O(log^{2}n) O(log2n),总时间复杂度是 O ( n l o g 2 n ) O(nlog^{2}n) O(nlog2n)。每次操作在一个根上最多插入一条长 O ( l o g n ) O(logn) O(logn)的链,每次操作修改了 O ( l o g n ) O(logn) O(logn)个根,于是总空间复杂度是 O ( n l o g 2 n ) O(nlog^{2}n) O(nlog2n)
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
int n,m;
struct question
{
string s;
int l,r,k;
}q[100005];
int a[100005],vv[200005];
int tree[40000005],cnt,version[100005];
int lson[40000005],rson[40000005],vp,len;
inline int lowbit(int x){return -x&x;}
inline int& ls(int x){return lson[x];}
inline int& rs(int x){return rson[x];}
inline void push_up(int x){tree[x]=tree[ls(x)]+tree[rs(x)];}
void update(int nl,int l,int r,int x,int k)
{
if(l==r){tree[x]+=k;return;}
int mid=l+r>>1;
if(nl<=mid)
{
if(!ls(x)) ls(x)=++cnt;
update(nl,l,mid,ls(x),k);
}
else
{
if(!rs(x)) rs(x)=++cnt;
update(nl,mid+1,r,rs(x),k);
}
push_up(x);
}
void add(int x,int k,int v)
{
while(x<=n)
{
if(!version[x]) version[x]=++cnt;
update(k,1,len,version[x],v);
x+=lowbit(x);
}
}
int tmp1[100005],pp1,tmp2[100005],pp2;
int solve(int l,int r,int x,int k)
{
// printf("x=%d\n",x);
if(l==r) return l;
int mid=l+r>>1,sum=0;
for(int i=1;i<=pp1;i++) sum-=tree[ls(tmp1[i])];
for(int i=1;i<=pp2;i++) sum+=tree[ls(tmp2[i])];
if(k<=sum)
{
for(int i=1;i<=pp1;i++) tmp1[i]=ls(tmp1[i]);
for(int i=1;i<=pp2;i++) tmp2[i]=ls(tmp2[i]);
return solve(l,mid,ls(x),k);
}
else
{
for(int i=1;i<=pp1;i++) tmp1[i]=rs(tmp1[i]);
for(int i=1;i<=pp2;i++) tmp2[i]=rs(tmp2[i]);
return solve(mid+1,r,rs(x),k-sum);
}
}
int solve(int l,int r,int k)
{
pp1=pp2=0;
for(int i=l-1;i;i-=lowbit(i)) tmp1[++pp1]=version[i];
for(int i=r;i;i-=lowbit(i)) tmp2[++pp2]=version[i];
return vv[solve(1,len,1,k)];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
vv[++len]=a[i];
}
for(int i=1;i<=m;i++)
{
auto& [s,l,r,k]=q[i]; cin>>s;
if(s=="Q") cin>>l>>r>>k;
else
{
cin>>l>>r;
vv[++len]=r;
}
}
sort(vv+1,vv+1+len); len=unique(vv+1,vv+1+len)-vv-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(vv+1,vv+1+len,a[i])-vv;
for(int i=1;i<=m;i++)
if(q[i].s=="C") q[i].r=lower_bound(vv+1,vv+1+len,q[i].r)-vv;
for(int i=1;i<=n;i++) add(i,a[i],1);
for(int i=1;i<=m;i++)
{
auto& [s,l,r,k]=q[i];
if(s=="Q") printf("%d\n",solve(l,r,k));
else
{
add(l,a[l],-1);
add(l,a[l]=r,1);
}
}
return 0;
}