静态区间第K小
给出一段长度序列 a[1]、a[2]、… 、a[N],每次询问 [L, R] 区间内第K小的数为?
我们可以建立 权值线段树,线段树内存储 某个权值的出现次数。
对于 [L, R] 区间(a[L] ~ a[R]),构建的线段树
以下示例参考blog:https://www.cnblogs.com/LiuRunky/p/Sustainable_Segment_Tree.html
例如:数列a={10,20,30,20,50,10,60,40},离散化后得到b={1,2,3,2,5,1,6,4}
对于数列内每一个离散化后的数,我们建立一个基于数值的 区间和线段树 统计它的出现次数
(7、8是用来占位的,可以无视)
这样,我们可以通过类似二分的思想找到第k小,而线段树的节点已经帮助我们将区间对半切分
假设我们想找区间第7小:
step 1: 区间[1,4]内的数一共出现了6次,所以我们可以直接进入另一区间[5,8],并且找这个区间中的第1小
step 2: 区间[5,6]内的数一共出现了2次,所以[5,8]中的第1小一定也是[5,6]中的第1小
step 3: 区间[5,5]内的数一共出现了1次,所以5正是[5,6]中的第1小,即整个查询区间中的第7小
那要怎么得到区间[L, R]的线段树呢,这里就要用到主席树,先建个出现次数全为0的空树,然后依次更新加入 a[1]、a[2]、… 、a[N],那我们就得到了N个版本的权值线段树(可以看作一个 树形的前缀和)
对于任意一个结点,只需要用 第R个版本 和 第L-1个版本 的线段树同位置结点相减,从L-1 到R这个结点增加的值,既是 [L, R]的线段树的值。
模板题:POJ2104 - K-th Number
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e5+50;
int n,m,N,a[maxn],b[maxn],root[maxn];
int t[maxn<<5],ls[maxn<<5],rs[maxn<<5],cnt=0;
int build(int l,int r)
{
int rt=++cnt;
if(l==r)
{
t[rt]=0;
return rt;
}
int mid=(l+r)>>1;
ls[rt]=build(l,mid);
rs[rt]=build(mid+1,r);
t[rt]=t[ls[rt]]+t[rs[rt]];
return rt;
}
int updata(int l,int r,int pos,int pre)
{
int rt=++cnt;
if(l==r)
{ //出现次数+1
t[rt]=t[pre]+1; //注意,不是t[rt]++
return rt;
}
ls[rt]=ls[pre],rs[rt]=rs[pre];
int mid=(l+r)>>1;
if(pos<=mid)
ls[rt]=updata(l,mid,pos,ls[pre]);
else
rs[rt]=updata(mid+1,r,pos,rs[pre]);
t[rt]=t[ls[rt]]+t[rs[rt]];
return rt;
}
int query(int l,int r,int rt1,int rt2,int K)
{
if(l==r)
return l; //返回对应的权值
int suml=t[ls[rt2]]-t[ls[rt1]]; //左子树出现次数之和
int mid=(l+r)>>1;
if(suml>=K)
return query(l,mid,ls[rt1],ls[rt2],K); //L-1树和R树的rt要一同更新
else
return query(mid+1,r,rs[rt1],rs[rt2],K-suml); //注意变为了K-suml
}
void init()
{
for(int i=1;i<=n;i++)
b[i]=a[i];
sort(b+1,b+n+1);
N=unique(b+1,b+n+1)-(b+1); //离散化以后不同数的个数
for(int i=1;i<=n;i++) //即离散化后变为1 ~ N
a[i]=lower_bound(b+1,b+N+1,a[i])-b;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
init(); //离散化
root[0]=build(1,N); //先建个空树
for(int i=1;i<=n;i++)
root[i]=updata(1,N,a[i],root[i-1]); //依次更新加入
for(int i=1;i<=m;i++)
{
int L,R,K;
scanf("%d %d %d",&L,&R,&K);
printf("%d\n",b[query(1,N,root[L-1],root[R],K)]);
}
return 0;
}

本文介绍如何使用主席树解决区间第K小问题,通过构建权值线段树统计区间内元素出现次数,实现快速查找。利用离散化、二分思想及线段树节点值,高效获取[L,R]区间内第K小的数。


384

被折叠的 条评论
为什么被折叠?



