【线段树】牛客 k小数查询 (区间线段树套权值线段树)

K小数查询

题意:

给一个长度为 n 数列 A ,然后m个操作,有如下两种:
· 1 l r x,表示对i\(\in\)[l,r],令 \(A_i=min(A_i,x)\)
· 2 l r k,表示询问区间[l,r]中第 k 小的数。

输入描述:

第一行输入两个整数 \(n,m(1≤n,m≤8×10^4)\)
接下来一行 n 个整数描述数组 \(A(1≤A_i≤n)\)
接下来 m 行每行描述一个操作,操作格式与题面中相同,保证\(1≤l≤r≤n,1≤k≤r−l+1,1≤x≤10^9\)

输出描述:

对于每组询问,输出一个整数表示答案。

官方题解:

考虑区间线段树套权值线段树求区间k小值的算法:对 \([1,n]\) 建线段树,每一个线段树节点 \([l,r]\) ,用一棵动态开点的权值线段树记录 \([l,r]\) 中每种权值出现了多少次。
如果能够维护这样的数据结构,询问就可以转化为在 \(O(logn)\) 棵权值线段树上二分,能在 \(O(lon^2n)\) 的时间里得到答案。
修改时,首先和普通的区间线段树一样,定位到某些节点[l,r]:这次修改相当于把内层线段树中所有大于等于 \(x\) 的数并到 \(x\) 的位置,这可以被转化成 \(t+1\) 次线段树单点修改操作,其中 \(t\) 为被并入的节点个数。把这 \(t+1\) 次修改应用到内层线段树 \([l,r]\) 以及 \([l,r]\) 所有祖先节点的内层线段树上。这一步的时间复杂度为 \(O(tlog^2n)\)
接着,在 \([l,r]\) 上打上对 \(x\)\(min\) 的标记。在标记下传的时候,同样当前节点的内层线段树中,更大的节点的值并到 \(x\) 处,但是因为次数祖先节点的内层线段树的信息都是正确的,所以不需要把修改应用到祖先节点的内层线段树上。
时间复杂度和所有操作的 \(t\) 的和有关,不难发现,每一次操作结束后,内层线段树的叶子节点个数都减少了 ,而最开始一共有 \(O(nlogn)\) 个叶子节点,所以 \(t\) 的总和不会超过 \(O(nlogn)\) 。因此总的时间复杂度为 \(O(nlog^3n)\)



个人:

对于动态开点建权值线段树 练习参考luogu3939以及luogu1383
主要在于题解说 : 每一次操作结束后,内层线段树的叶子节点个数都减少了 ,而最开始一共有 \(O(nlogn)\) 个叶子节点,所以 \(t\) 的总和不会超过 \(O(nlogn)\)。所以复杂度没有看起来那么高。
比较难考虑到的是题解的up函数(就是上传内函数的修改)。
....好吧 整套思路还是难想 因为是二分答案+树套树... (加油(ง •_•)ง


【看着题解又又又胡搞了份难看的代码,然后神奇的过了

代码如下

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e7+5;
const int inf=0x3f3f3f3f;

int n;
int T[maxn],L[maxn],R[maxn],sum[maxn],tot;
int mi[maxn],up[maxn];
//对每个区间 动态开点 建权值线段树
inline void add(int&rt,int l,int r,int x,int v)
{
    if(!rt)rt=++tot;
    if(l==r)
    {
        sum[rt]+=v;return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)add(L[rt],l,mid,x,v);
    else add(R[rt],mid+1,r,x,v);
    sum[rt]=sum[L[rt]]+sum[R[rt]];
}
inline int get(int rt,int l,int r,int tl,int tr)
{
    if(!rt)return 0;
    if(tl<=l&&r<=tr)return sum[rt];
    int mid=(l+r)>>1,res=0;
    if(tl<=mid)res+=get(L[rt],l,mid,tl,tr);
    if(tr>mid)res+=get(R[rt],mid+1,r,tl,tr);
    return res;
}
inline void build(int k,int l,int r,int x,int v)
{
    mi[k]=inf;
    add(T[k],1,n,v,1);
    up[k]=max(up[k],v);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(x<=mid)build(k<<1,l,mid,x,v);
    else build(k<<1|1,mid+1,r,x,v);
}

/*
 op==1: [l,r], Ai=min(Ai,x)
 等效把[l,r]区间里的大于x的数 并入x的计数,并清零
 */
inline void down(int k)
{
    int s;
    if(mi[k]<up[k<<1])
    {
        s=get(T[k<<1],1,n,mi[k]+1,up[k<<1]);
        add(T[k<<1],1,n,mi[k],s);
        mi[k<<1]=up[k<<1]=mi[k];
    }
    if(mi[k]<up[k<<1|1])
    {
        s=get(T[k<<1|1],1,n,mi[k]+1,up[k<<1|1]);
        add(T[k<<1|1],1,n,mi[k],s);
        up[k<<1|1]=mi[k<<1|1]=mi[k];
    }
    mi[k]=inf;
}
inline void upp(int k,int x,int num)
{
    add(T[k],1,n,x,num);
    if(k>>1)upp(k>>1,x,num);
}
inline void clear(int rt,int k,int l,int r,int x,int num)
{
    if(l==r)
    {
        sum[rt]-=num;
        if(k>>1)clear(T[k>>1],k>>1,1,n,x,num);
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)clear(L[rt],k,l,mid,x,num);
    else clear(R[rt],k,mid+1,r,x,num);
    sum[rt]=sum[L[rt]]+sum[R[rt]];
}
inline void update(int k,int l,int r,int tl,int tr,int x)
{
    if(x>=up[k])return ;
    if(tl<=l&&r<=tr)
    {
        int sum,num;
        sum=get(T[k],1,n,x+1,up[k]);
        if(sum)upp(k,x,sum);
        for(int i=x+1;i<=up[k]&&sum;i++)
        {
            num=get(T[k],1,n,i,i);sum-=num;
            if(num)clear(T[k],k,1,n,i,num);
        }
        up[k]=mi[k]=x;
        return;
    }
    if(mi[k]!=inf)down(k);
    int mid=(l+r)>>1;
    if(tl<=mid)update(k<<1,l,mid,tl,tr,x);
    if(tr>mid)update(k<<1|1,mid+1,r,tl,tr,x);
}
inline int query(int k,int l,int r,int tl,int tr,int x)
{
    if(tl<=l&&r<=tr)
    {
        if(x>=up[k])return get(T[k],1,n,1,up[k]);
        else return get(T[k],1,n,1,x);
    }
    if(mi[k]!=inf)down(k);
    int mid=(l+r)>>1,res=0;
    if(tl<=mid)res+=query(k<<1,l,mid,tl,tr,x);
    if(tr>mid)res+=query(k<<1|1,mid+1,r,tl,tr,x);
    return res;
}
int main()
{
    int m,x,op,l,r,L,R,mid,t,res;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        build(1,1,n,i,x);
    }
    while(m--)
    {
        scanf("%d%d%d%d",&op,&l,&r,&x);
        if(op==1)update(1,1,n,l,r,x);
        else
        {
            L=1,R=n,res=-1;
            while(L<=R)
            {
                mid=(L+R)>>1;
                t=query(1,1,n,l,r,mid);
//                printf("#%d %d %d\n",mid,t,x);
                if(t>=x)
                {
                    res=mid;
                    R=mid-1;
                }
                else L=mid+1;
            }
            printf("%d\n",res);
        }
    }
}



猜你喜欢

转载自www.cnblogs.com/kkkek/p/12244522.html