[SP3266]KQUERY - K-query

\(n\)才3万主席树个锤子哦。。。

介绍一种最简单的写法——归并树。

归并树是一种线段树,它的特殊之处在于,它的结点维护的信息并不是什么最大值区间和什么什么的,而是一个数列,代表该节点维护的区间排好序的结果。

由于这样的整棵线段树看上去就像归并排序一样,因此得名归并树

建树的复杂度和归并排序一样是\(O(n\log n)\)的,这里不提了。

查询的话,众所周知区间\([l,r]\)中小于等于\(k\)的数的个数相当于区间\([l,p]\)与区间\([p+1,r]\)中小于等于\(k\)的数的个数。这样就可以扔给两个(与这个区间有交集的)子结点做啦!但如果当前结点的区间被查询区间完全包含的话,那就不能推卸责任了,直接在这个结点的数列上二分查找即可找到答案了。查询复杂度\(O(\log^2 n)\)/次。

总复杂度:\(O(n\log n + q\log^2 n)\)

更具体的描述详见:https://strncmp.blog.luogu.org/solution-sp3946

#include
#include
#include
using namespace std;

const int N=1e5+7;
int Array[N];
namespace SGTree{
    int L[N<<2],R[N<<2];
    vector dat[N<<2];
    
    void build(int l,int r,int rt=1)
    {
        L[rt]=l,R[rt]=r;
        if(l==r)
        {
            dat[rt].resize(1);
            dat[rt][0]=Array[l];
            return;
        }
        int mid=(l+r)>>1;
        build(l,mid,rt<<1),build(mid+1,r,rt<<1|1);
        dat[rt].resize(r-l+1);
        merge(dat[rt<<1].begin(),dat[rt<<1].end(),
              dat[rt<<1|1].begin(),dat[rt<<1|1].end(),
              dat[rt].begin());
    }
    int query(int l,int r,int x,int rt=1)
    {
        if(l<=L[rt]&&R[rt]<=r)
            return upper_bound(dat[rt].begin(),dat[rt].end(),x)
                  -dat[rt].begin();
        int mid=(L[rt]+R[rt])>>1,ret=0;
        if(l<=mid) ret+=query(l,r,x,rt<<1);
        if(r>mid) ret+=query(l,r,x,rt<<1|1);
        return ret;
    }
}
int n,m;

signed main()
{
    scanf("%d",&n);
    for(register int i=1;i<=n;i++)
        scanf("%d",Array+i);
    SGTree::build(1,n);
    sort(Array+1,Array+1+n);
    int i,j,k;
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d%d%d",&i,&j,&k);
        printf("%d\n",(j-i+1)-SGTree::query(i,j,k));
    }
    return 0;
}

求赞QwQ。

你可能感兴趣的:([SP3266]KQUERY - K-query)