前置知识:值域线段树,可持续化线段树,树状数组
动态整体第k小
题目:给定一个序列和m次操作,每次操作修改单点或者询问整个序列第k小的数
首先考虑暴力,对于每次修改都直接排序的话,复杂度为O(nmlogn),也可以魔改一下排序方法,不过一般的暴力还是没办法过
整体第k小带修改很明显可以用平衡树做,不过编程较麻烦(而且大材小用),所以不考虑
值域线段树
值域线段树可以很方便的O(logn)查询一次所有数中比某个值小的数的个数,于是我们可以考虑用它解决这一类问题,当然一般来说值域线段树是和离散化配套使用的
做法:
将所有数离散化后加入值域线段树,修改操作就直接删除旧的,加入新的
对于查询操作,从根节点开始,当前节点的左儿子保存着\(≤\)mid的数的个数sum,如果sum\(≥\)k,就说明第k小应该在左边,递归到左儿子,否则k-=sum,递归给右儿子(k-=sum是因为在整个区间找第k小等价于在右区间找第k-sum小)
时间复杂度为O(nlogn),空间复杂度O(n*4)
静态前缀第k小
题目:每次查询前x个数中的第k小,无修改
做法:这里改变一下上面的方法。上面的做法中,可以发现,sum的大小表示的是所有数中\(≤\)mid的数的个数,而这里是要求前x个数中\(≤\)mid的数的个数,于是我们需要对每一个数a[i]加入之后都对前i个数建立一颗值域线段树,询问前x个数的时候就使用第x个线段树
可持续化线段树
显然不可能真的建立n个值域线段树
时空复杂度O(nlogn)
静态区间第k小
题目:查询改为[ l , r ]区间,无修改
首先明确一件事,对于上面建的n个值域线段树(假装把n个树都单独拆出来),形态完全相同,并且对于每一个树的相同位置,意义几乎一样,比如,第x个树和第y个树的某个位置都表示不大于c的数的个数,只不过一个是针对前a[1~x],另一个a[1~y]。所以可以考虑前缀和的思想,假设y \(>\) x,用y树一个节点减去x树上对应节点就可以表示a[ x+1 ~ y ]这一段上不大于c的数
做法:
对于查询[ l , r ],同时使用l-1和r两个值域线段树,每次的sum由r树的左儿子减去l-1树的左儿子得到,向下递归时两个根要一起向同一个方向走
时空复杂度O(nlogn)
Code:
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,m;
int ref[N],len;
int a[N],ndsum;
int root[N],ls[N*20],rs[N*20],sum[N*20];
template <class T>
void read(T &x)
{
char c;int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void build(int &rt,int l,int r)
{
rt=++ndsum;
if(l==r) return;
int mid=(l+r)>>1;
build(ls[rt],l,mid);
build(rs[rt],mid+1,r);
}
void copynode(int x,int y)
{
ls[x]=ls[y];
rs[x]=rs[y];
sum[x]=sum[y]+1;//复制的链上都会增加 1
}
int modify(int rt,int l,int r,int x,int val)
{
int t=++ndsum;
copynode(t,rt);
if(l==r) return t;
int mid=(l+r)>>1;
if(mid>=x) ls[t]=modify(ls[rt],l,mid,x);
else rs[t]=modify(rs[rt],mid+1,r,x);
return t;
}
int query(int rt1,int rt2,int l,int r,int k)
{
if(l==r) return l;
int x=sum[ls[rt2]]-sum[ls[rt1]];
int mid=(l+r)>>1;
if(x>=k) return query(ls[rt1],ls[rt2],l,mid,k);
else return query(rs[rt1],rs[rt2],mid+1,r,k-x);
}
int main()
{
read(n);read(m);
for(int i=1;i<=n;++i) read(a[i]),ref[++len]=a[i];
sort(ref+1,ref+len+1);
len=unique(ref+1,ref+len+1)-ref-1;
build(root[0],1,len);//先建立一个空树
for(int i=1;i<=n;++i)
{
int t=lower_bound(ref+1,ref+len+1,a[i])-ref;//找到要加入的a[i]在ref中对应的下标
root[i]=modify(root[i-1],1,len,t);
}
for(int i=1;i<=m;++i)
{
int x,y,k;
read(x);read(y);read(k);
printf("%d\n",ref[query(root[x-1],root[y],1,len,k)]);
}
return 0;
}
动态区间第k小
待更新