题意:给出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;
}