K-th Number POJ - 2104

主席树板子题。

这是我做的第一道主席树的题。

前置知识:线段树,权值线段树。

别看了,我写的不好,看不懂的23333。

对于这道题来说,将所有数离散化后,值域最大为n,修改为n次,每次更新都是单点更新。

主席树是一个存着所有历史版本的线段树的一种数据结构(可持久化线段树),就对于这道题来说,每一颗线段树都存着值域上的信息。

最开始新建一颗普通的权值线段树,然后按照序列顺序向里面添加数,每更新一个数,我们就新建一颗线段树,即新建的线段树保存着之前所有的更新以及这次更新的所有更新操作带来的总的影响,而对于单点修改来说,每一次修改线段树中至多会有{log_{2}}^{n}个点发生改变,那么对于新建的线段树来说,我们只新建发生改变的点加到这颗树中未发生改变的点复制过来即可。

那么空间需要多大呢,一颗普通的线段树一般要开4*n的空间,我们修改了n次,每次修改新建了logn个节点,故主席树总的空间大小为:n*4+n*{log_{2}}^{n}

而查询[l,r]区间内的第k大数的话就相当于是在总共r次修改带来的影响去掉l-1(包括l-1)次操作的影响。

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

using namespace std;

typedef long long ll;

const int maxn=1e5+7;
int b[maxn],a[maxn];
int m;
int n,q;
void quchong(int n){
    //memcpy(b,a,sizeof(a));
    sort(b+1,b+1+n);
    m=unique(b+1,b+1+n)-b-1;
}

int getid(int x){
    return lower_bound(b+1,b+1+m,x)-b;
}

int root[2061118];//存储每次修改的树的首节点的编号;
int tot;

struct Tree{
    int lc,rc;
    int sum;
}tree[2061118];

int build(int l,int r){
    int k=++tot;
    if(l==r){
        tree[k].sum=0;
        return k;
    }
    int mid=(l+r)>>1;
    tree[k].lc=build(l,mid);
    tree[k].rc=build(mid+1,r);
    tree[k].sum=tree[tree[k].lc].sum+tree[tree[k].rc].sum;
    return k;
}

int updata(int now,int l,int r,int id,int val){
    int k=++tot;
    tree[k]=tree[now];
    if(l==r){
        ++tree[k].sum;
        return k;
    }
    int mid=(l+r)>>1;
    if(id<=mid)
        tree[k].lc=updata(tree[now].lc,l,mid,id,val);
    else
        tree[k].rc=updata(tree[now].rc,mid+1,r,id,val);
    tree[k].sum=tree[tree[k].lc].sum+tree[tree[k].rc].sum;
    return k;
}

int myfind(int L,int R,int l,int r,int h){
    if(l==r) return l;
    int val=tree[tree[R].lc].sum-tree[tree[L].lc].sum;
    int mid=(l+r)>>1;
    if(h<=val) return myfind(tree[L].lc,tree[R].lc,l,mid,h);
    else return myfind(tree[L].rc,tree[R].rc,mid+1,r,h-val);
}


int main(){
    //cout<<(int)(maxn*4+maxn*log2(maxn))<<endl;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    quchong(n);
    tot=0;
    root[0]=build(1,m);
    for(int i=1;i<=n;++i)
        root[i]=updata(root[i-1],1,m,getid(a[i]),1);
    int l,r,k;
    while(q--){
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",b[myfind(root[l-1],root[r],1,m,k)]);
    }

    return 0;
}

还有一道相似的题,是求区间中小于等于k的个数,那么就相当于是求离散化后1~getid(k)内数出现的次数。

HDU 4417:http://acm.hdu.edu.cn/showproblem.php?pid=4417

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

using namespace std;

typedef long long ll;

const int maxn=1e5+7;
int b[maxn],a[maxn];
int m;
int n,q;
void quchong(int n){
    //memcpy(b,a,sizeof(a));
    sort(b+1,b+1+n);
    m=unique(b+1,b+1+n)-b-1;
}

int getid(int x){
    return lower_bound(b+1,b+1+m,x)-b;
}

int root[2061118];
int tot;

struct Tree{
    int lc,rc;
    int sum;
}tree[2061118];

int build(int l,int r){
    int k=++tot;
    if(l==r){
        tree[k].sum=0;
        return k;
    }
    int mid=(l+r)>>1;
    tree[k].lc=build(l,mid);
    tree[k].rc=build(mid+1,r);
    tree[k].sum=tree[tree[k].lc].sum+tree[tree[k].rc].sum;
    return k;
}

int updata(int now,int l,int r,int id,int val){
    int k=++tot;
    tree[k]=tree[now];
    if(l==r){
        ++tree[k].sum;
        return k;
    }
    int mid=(l+r)>>1;
    if(id<=mid)
        tree[k].lc=updata(tree[now].lc,l,mid,id,val);
    else
        tree[k].rc=updata(tree[now].rc,mid+1,r,id,val);
    tree[k].sum=tree[tree[k].lc].sum+tree[tree[k].rc].sum;
    return k;
}

//int myfind(int L,int R,int l,int r,int h){
//    if(l==r) return l;
//    int val=tree[tree[R].lc].sum-tree[tree[L].lc].sum;
//    int mid=(l+r)>>1;
//    if(h<=val) return myfind(tree[L].lc,tree[R].lc,l,mid,h);
//    else return myfind(tree[L].rc,tree[R].rc,mid+1,r,h-val);
//}
int myfind(int p,int q,int l,int r,int L,int R){
    if(l>=L&&r<=R) return tree[p].sum-tree[q].sum;
    int mid=(l+r)>>1;
    int res=0;
    if(L<=mid) res+=myfind(tree[p].lc,tree[q].lc,l,mid,L,R);
    if(R>mid) res+=myfind(tree[p].rc,tree[q].rc,mid+1,r,L,R);
    return res;

}

int idd(int x){
    int l=1,r=m,mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(b[mid]>x) r=mid-1;
        else l=mid+1;
    }
    return r;

}

int main(){
    //cout<<(int)(maxn*4+maxn*log2(maxn))<<endl;
    int t;
    scanf("%d",&t);
    int cc=0;
    while(t--){

        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
            b[i]=a[i];
        }
        quchong(n);
        tot=0;
        root[0]=build(1,m);
        for(int i=1;i<=n;++i)
            root[i]=updata(root[i-1],1,m,getid(a[i]),1);
        int l,r,k;
        printf("Case %d:\n",++cc);
        while(q--){
            scanf("%d%d%d",&l,&r,&k);
            ++l,++r;
            int id=idd(k);
            if(id==0) printf("0\n");
            else printf("%d\n",myfind(root[r],root[l-1],1,m,1,id));

        }
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值