POJ 2104 K-th Number(可持久化线段树)

本文详细介绍了可持久化线段树的概念及其在解决区间查询问题中的应用,并通过实例展示了如何使用该数据结构来查询特定更新次数下区间内的最大值及区间内第K小的数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给出n个数,查询 l,r 区间内的第K小的数;
这道题可以用主席树,也就是可持久化线段树来解决,所以大概还是要学一下什么叫做可持久化线段树。
可持久化线段树基于普通的线段树延伸而成,普通的线段树往往能解决在更新后某段区间内的查询问题,但是不能知道例如第几次更新时区间问题,而可持久化就是来解决此类问题。
每一次更新,我们可以来新建一颗线段树维护此时的信息,那么n次查询就有n棵树,内存就已经炸掉了。
但是我们单独看某一次更新,从根节点一直到叶子节点再维护到根节点,整个维护中只有一条链上面的点被修改了,我们仅仅只新建这些被修改的点,而其他的点则不变依然指向前一颗树,以这样的方式来新建一棵树,就节约了很多内存,再来看根节点,每一次更新就新建一个根节点,要查询时就按照第几次的根节点来查询,所以就要动态的建点。
大致代码如下,裸的查询第几次更新后的区间最大值(未验证正确性):

#include<stdio.h>
#include<algorithm>
#include<string>
#include<string.h>
#include<queue>
#include<stack>
#include<math.h>
#include<map>
#include<iostream>
using namespace std;
#define ll long long
#define maxn 200005
int cnt,a[maxn];
int root[maxn];
struct node
{
    int l;
    int r;
    int smax;
}tree[maxn*4];
void build(int id,int l,int r)
{
   if(l==r)
   {
       tree[id].smax=a[l];
       return ;
   }
   tree[id].l=cnt++;
   tree[id].r=cnt++;
   int mid=(l+r)/2;
   build(tree[id].l,l,mid);
   build(tree[id].r,mid+1,r);
   tree[id].smax=max(tree[tree[id].l].smax,tree[tree[id].r].smax);
}
void update(int id,int l,int r,int x,int z)
{
    int mid=(l+r)/2;
    if(l==r)
    {
        tree[id].smax=z;
        return ;
    }
    if(x<=mid)
        update(tree[id].l,l,mid,x,z);
    else
        update(tree[id].r,mid+1,r,x,z);
    tree[id].smax=max(tree[tree[id].l].smax,tree[tree[id].r].smax);
}
void new_tree(int id,int tmpid,int l,int r,int x,int y)
{
    int mid=(l+r)/2;
    if(l==r)
    {
        tree[id].smax=y;
        return ;
    }
    if(x<=mid)
    {
        tree[id].l=cnt++;
        tree[id].r=tree[tmpid].r;
        new_tree(tree[id].l,tree[tmpid].l,l,mid,x,y);
    }
    else
    {
        tree[id].l=tree[tmpid].l;
        tree[id].r=cnt++;
        new_tree(tree[id].r,tree[tmpid].r,mid+1,r,x,y);
    }
    tree[id].smax=max(tree[tree[id].l].smax,tree[tree[id].r].smax);
}
int query(int id,int l,int r,int x,int y)
{
    int lson=tree[id].l;
    int rson=tree[id].r;
    int mid=(l+r)/2;
    if(x<=l&&r<=y)
        return tree[id].smax;
    if(y<=mid)
        return query(lson,l,mid,x,y);

    else if(x>=mid+1)
        return query(rson,mid+1,r,x,y);
    else
        return max(query(lson,l,mid,x,y),query(rson,mid+1,r,x,y));
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        cnt=1;
        for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
        build(0,1,n);
        int now=0;
        root[0]=0;
        while(m--)
        {
            char c[2];
            scanf("%s",c);
            if(c[0]=='Q')
            {
                int x,y,t;
                scanf("%d%d%d",&t,&x,&y);
                int ans=query(root[t],1,n,x,y);
                printf("%d\n",ans);
            }
            else
            {
                int x,y;
                now++;
                scanf("%d%d",&x,&y);
                root[now]=cnt++;
                new_tree(root[now],root[now-1],1,n,x,y);
            }
        }
    }
return 0;
}

再来看原题:求出某区间第K小的数
如果要查询(L,R)区间的第K大的数,我们用二分来实现,首先把区间内出现的数用数组统计,二分pos, 表示在数组1到pos 中有多少个数,如果总数等于K,那么pos这个数就是区间第K小的数,如果总数大于K,那么就在整个区间的左边,反之则在右边,这是用二分来实现,但是题目数的范围在1e9以内,但是数的总数不超过1e5,所以先离散化,再统计数目,然后再来考虑一个问题,怎么快速统计(L,R)区间内每个离散后的数的个数,这里就可以用可持久化线段树来维护,我们以(1,i)区间按值建立n棵树,那么第R棵树减去第L-1棵树剩下的就是(L,R)区间数的信息,查询时就可以查询区间内的总数了。


#include<stdio.h>
#include<algorithm>
#include<string>
#include<string.h>
#include<queue>
#include<vector>
#include<stack>
#include<math.h>
#include<map>
#include<iostream>
using namespace std;
#define maxn 100020
int n,m,a[maxn];
int cnt,root[maxn];
struct node
{
    int l;
    int r;
    int smax;
}tree[maxn*45];
 vector<int> v;
int getid(int x){   return lower_bound(v.begin(),v.end(),x)-v.begin()+1;    }
void build(int id,int l,int r)
{
   if(l==r)
   {
       tree[id].smax=0;
       return ;
   }
   tree[id].l=cnt++;
   tree[id].r=cnt++;
   int mid=(l+r)/2;
   build(tree[id].l,l,mid);
   build(tree[id].r,mid+1,r);
   tree[id].smax=tree[tree[id].l].smax+tree[tree[id].r].smax;
}
void new_tree(int id,int tmpid,int l,int r,int x,int y)
{
    int mid=(l+r)/2;
    if(l==r)
    {
        tree[id].smax+=y;
        return ;
    }
    if(x<=mid)
    {
        tree[id].l=cnt++;
        tree[id].r=tree[tmpid].r;
        new_tree(tree[id].l,tree[tmpid].l,l,mid,x,y);
    }
    else
    {
        tree[id].l=tree[tmpid].l;
        tree[id].r=cnt++;
        new_tree(tree[id].r,tree[tmpid].r,mid+1,r,x,y);
    }
    tree[id].smax=(tree[tree[id].l].smax+tree[tree[id].r].smax);
}
int Query(int lid,int rid,int l,int r,int k)
{
    if(l==r)
        return l;
    int mid=(l+r)/2;
    int s=tree[tree[rid].l].smax-tree[tree[lid].l].smax;
    if(k<=s)
        return Query(tree[lid].l,tree[rid].l,l,mid,k);
    else
        return Query(tree[lid].r,tree[rid].r,mid+1,r,k-s);
}
int main()
{
    int t=1;
    while(t--)
    {
        cnt=1;
        v.clear();
        memset(root,0,sizeof(root));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            v.push_back(a[i]);
        }
        build(0,1,n);
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        root[0]=0;
        for(int i=1;i<=n;i++)
        {
            root[i]=cnt++;
            new_tree(root[i],root[i-1],1,n,getid(a[i]),1);
        }
        for(int i=1;i<=m;i++)
        {
            int x,y,k;
            scanf("%d%d%d",&x,&y,&k);
            int ans=Query(root[x-1],root[y],1,n,k);
            printf("%d\n",v[ans-1]);
        }
    }
return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值