区间第k小算法学习笔记

前置知识:值域线段树,可持续化线段树,树状数组

动态整体第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个线段树

扫描二维码关注公众号,回复: 5954204 查看本文章

可持续化线段树

显然不可能真的建立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小

待更新

猜你喜欢

转载自www.cnblogs.com/Chtholly/p/10740533.html
今日推荐