【POJ2104】K-th Number-主席树(可持久化线段树)+离散化

测试地址:K-th Number

题目大意:给定一个长度为N的数列,要求处理M个询问,每个询问三个参数i,j,k,表示询问区间[i,j]内第k小的数。

做法:可持久化线段树的单点修改,网上有很多这里就不讲了。这里再简述一下这一题如何转化成可持久化线段树。首先将数列里所有数离散化成1,2,...,N,可持久化线段树的每一个节点维护一个权值区间,再维护一个cnt表示这个区间内的数出现过了多少次,一开始所有节点的cnt都初始化成0,建立一棵初始(第0棵)线段树。然后我们逐个加入原数列中的元素A[i](当然是离散化后的),这就等价于对线段树进行单点修改并产生一个新的版本,具体来说就是复制所有包含A[i]的权值区间的节点,然后在新复制的节点上的cnt增加1。这时候我们就可以知道,第i棵线段树上表示[l,r]这个权值区间的节点中cnt的意义就是:在原数列的区间[1,i]中,权值在权值区间[l,r]中的元素个数。

加入所有元素之后,我们就可以用二分查找来询问区间[i,j]的第k小值。要进行二分查找,首先要算出在原数列的区间[i,j]中权值在某一个权值区间中的元素个数,由前面的cnt的意义可知,N棵线段树其实是一种前缀和,因此把第j棵线段树与第i-1棵线段树的对应节点cnt值相减,所得到的线段树就可以表示出在原数列的区间[i,j]中权值在某一个权值区间中的元素个数。这棵线段树不用存起来,可以在二分查找时用相对应的节点直接相减得到。知道这一点之后,就可以在这棵线段树上对权值区间[1,N]进行二分查找,如果权值区间[1,N/2]中元素的出现次数不超过k,说明第k小的值在区间[1,N/2]中,从而继续向左子节点查找,否则说明第k小的值在区间(N/2,N]中,向右子节点查找。最后注意输出的值是离散化前的值即可。

可持久化线段树内存是硬伤......一定要开足数组!

上面讲了这么多,实际操作起来不难,代码连100行都不到,以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,a[100010],tot=0,rt[100010];
struct segnode
{
  int lc,rc;
  int cnt;
}seg[4000010];
struct forsort
{
  int val,pos;
}fs[100010];

bool cmp(forsort a,forsort b)
{
  return a.val<b.val;
}

void buildtree(int no,int l,int r)
{
  seg[no].cnt=0;
  tot=max(tot,no);
  if (l==r)
  {
    seg[no].lc=seg[no].rc=0;
	return;
  }
  seg[no].lc=no<<1,seg[no].rc=no<<1|1;
  int mid=(l+r)>>1;
  buildtree(no<<1,l,mid);
  buildtree(no<<1|1,mid+1,r);
}

void insert(int &no,int last,int l,int r,int x)
{
  no=++tot;
  seg[no].cnt=seg[last].cnt;
  seg[no].lc=seg[last].lc;
  seg[no].rc=seg[last].rc;
  if (l==r)
  {
    seg[no].cnt++;
	return;
  }
  int mid=(l+r)>>1;
  if (x<=mid) insert(seg[no].lc,seg[last].lc,l,mid,x);
  else insert(seg[no].rc,seg[last].rc,mid+1,r,x);
  seg[no].cnt=seg[seg[no].lc].cnt+seg[seg[no].rc].cnt;
}

int query(int fnt,int lst,int l,int r,int k)
{
  if (l==r) return l;
  int mid=(l+r)>>1,s=seg[seg[lst].lc].cnt-seg[seg[fnt].lc].cnt;
  if (s>=k) return query(seg[fnt].lc,seg[lst].lc,l,mid,k);
  else return query(seg[fnt].rc,seg[lst].rc,mid+1,r,k-s);
}

int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)
  {
    scanf("%d",&fs[i].val);
	fs[i].pos=i;
  }
  
  sort(fs+1,fs+n+1,cmp);
  for(int i=1;i<=n;i++)
    a[fs[i].pos]=i;
  buildtree(1,1,n);
  rt[0]=1;
  for(int i=1;i<=n;i++)
    insert(rt[i],rt[i-1],1,n,a[i]);
  for(int i=1,s,t,k;i<=m;i++)
  {
    scanf("%d%d%d",&s,&t,&k);
	printf("%d\n",fs[query(rt[s-1],rt[t],1,n,k)].val);
  }
  
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值