POJ - 2104 K-th Number(主席树模板讲解)

题意:

主席树模板:

给定一个数组,每次查询输入区间[L,R]的第k小的数。

题解:

主席树理解:
如有序列 1 2 5 1 3 2 2 5 1 2

我们要求 [5,10]5小的数

(数列中不存在4678 但根据原理就都写出来了,为方便理解,去掉了hash的步骤,实际的代码中其实只要一棵4个叶子节点的树即可)

(红色的为个数)

我们建立的[1, l-1](也就是[1, 4])之间的树为

[1, r]也就是[1,10]的树为

两树相减得到

我们来找第5小的数:

发现左子树为5  所以第5小的数在左边,再往下(41) 发现左边小于5,所以第5小的数在右边所以第5小的数就是3

 

同样的,我们只要建立[1,i] (i1n之间的所有值)的所有树,每当询问[l, r]的时候,只要用[1, r]的树减去[1, l-1]的树,再找第k小就好啦

我们将这n个树看成是建立在一个大的线段树里的,也就是这个线段树的每个节点都是一个线段树(——这就是主席树)

最初所有的树都是空树,我们并不需要建立n个空树,只要建立一个空树,也就是不必每个节点都建立一个空树

插入元素时,我们不去修改任何的结点,而是返回一个新的树(——这就是函数式线段树)

因为每个节点都不会被修改,所以可以不断的重复用,因此插入操作的复杂度为O(logn)

总的复杂度为O((n+m)lognlogN) 

 

 

你以为这样就结束了吗!!

你没有发现这样空间大到爆炸吗!!!

你在每个节点都建了一个线!段!树!这不MLE才有鬼呢!!!

那怎么办呢?

TiTi表示一棵[1, i]区间的线段树

那么TiTiTi−1Ti−1的区别就只有当前插入的这个元素aiai以及它的父亲以及他父亲的父亲以及他父亲的父亲的父亲...

也就是改变的就只有他和他上面logn个数

 

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

#define lson l, m
#define rson m+1, r
const int N=1e5+5;
int L[N<<5], R[N<<5], sum[N<<5];
int tot;
int a[N], T[N], Hash[N];
int build(int l, int r)
{
    int rt=(++tot);
    sum[rt]=0;
    if(l<r)
    {
        int m=(l+r)>>1;
        L[rt]=build(lson);
        R[rt]=build(rson);
    }
    return rt;
}

int update(int pre, int l, int r, int x)
{
    int rt=(++tot);
    L[rt]=L[pre], R[rt]=R[pre], sum[rt]=sum[pre]+1;
    if(l<r)
    {
        int m=(l+r)>>1;
        if(x<=m)
            L[rt]=update(L[pre], lson, x);
        else
            R[rt]=update(R[pre], rson, x);
    }
    return rt;
}

int query(int u, int v, int l, int r, int k)
{
    if(l>=r)
        return l;
    int m=(l+r)>>1;
    int num=sum[L[v]]-sum[L[u]];
    if(num>=k)
        return query(L[u], L[v], lson, k);
    else
        return query(R[u], R[v], rson, k-num);
}

int main()
{

    tot=0;
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++)
    {
        scanf("%d", &a[i]);
        Hash[i]=a[i];
    }
    sort(Hash+1, Hash+n+1);
    int d=unique(Hash+1, Hash+n+1)-Hash-1;
    T[0]=build(1, d);
    for(int i=1; i<=n; i++)
    {
        int x=lower_bound(Hash+1, Hash+d+1, a[i])-Hash;
        T[i]=update(T[i-1], 1, d, x);
    }
    while(m--)
    {
        int l, r, k;
        scanf("%d%d%d", &l, &r, &k);
        int x=query(T[l-1], T[r], 1, d, k);
        printf("%d\n", Hash[x]);
    }

}


另外附上挑战上归并树的解法:

 
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cstdio>

using namespace std;
#define lson i<<1,l,m
#define rson i<<1|1,m+1,r
const int maxn = 100111;
const int maxm = 5111;

int a[maxn];
int nums[maxn],L[maxn],R[maxn],K[maxn];
vector<int>dat[maxn<<2];

void pushup(int i)
{
    dat[i].resize(dat[i<<1].size()+dat[i<<1|1].size());
    merge(dat[i<<1].begin(),dat[i<<1].end(),dat[i<<1|1].begin(),dat[i<<1|1].end(),dat[i].begin() );
}
void build(int i,int l,int r)
{
    if(r==l)
    {
         dat[i].push_back(a[l]);
         return;
    }
    int m = (l+r)>>1;
    build(lson);
    build(rson);
    pushup(i);
}

int query(int ql,int qr,int x,int i,int l,int r)
{
    if(ql>r || qr<l)
        return 0;
    if(ql<=l && qr>=r)
        return upper_bound(dat[i].begin(),dat[i].end(),x)-dat[i].begin();

    int m = (l+r)>>1;
    int lc = query(ql,qr,x,lson);
    int rc = query(ql,qr,x,rson);
    return lc+rc;
}

int main()
{   int N,M;
    cin>>N>>M;
    for(int i=1;i<=N;i++)
        scanf("%d",a+i),nums[i]=a[i];
    build(1,1,N);
    sort(nums+1,nums+1+N);

    for(int i=0;i<M;i++)
    {
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        int lb=0,ub=N+1,mid;

        while(ub>lb+1)
        {
            mid=(lb+ub)>>1;
            if(query(l,r,nums[mid],1,1,N)<k)lb=mid;
            else ub=mid;
        }

        printf("%d\n",nums[ub]);
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值