区间k大问题是一个比较经典的问题,各种方法层出不穷,写暴力的、树套树的、主席树的、分块大法好(@LOI_DQS)的……
这里讲一下权值线段树+可持久化解决的方法(不支持修改操作)
首先是权值线段树,顾名思义,就是按权值大小保存而不是按照序列顺序保存的线段树,如1,4,2,5,3,在权值线段树里保存的顺序应该为1,2,3,4,5
可持久化数据结构,就是指可以“持久”的数据结构,具体就是假设你现在做到了第100步,却突然想查询第50步时的状态(听起来很坑对不对)
可持久化如何实现呢?想具体了解的可以看wc2012 FHQ的论文《谈谈各种数据结构》,大概比较靠后的位置;不想具体了解的可以看我讲的……
Q:可持久化最简单的实现方法是什么?
A:[手动斜眼]每次操作后的数据结构存一遍,操作N次就存N棵树
Q:说的吼!但是空间太大了怎么办?
A:……
Q:一个数据结构修改要动多少东西?就拿线段树说吧
A:修改一次要改logN个节点的值
Q:那么剩下那些不用改的节点还用存吗?
A:……似乎不用了
Q:那么修改一次就等于把所有修改的节点另建立新节点,对吗?
A:我好像明白了些什么…………
![]()
黄色节点为要修改的节点,红色节点为新建的节点,黑色圈里圈着的是旧树,红色的圈里圈着的是新树,可以看到,修改后的新树只是在旧树的基础上修改了几个节点,所以只需要新建这几个节点,并把新树的部分指向指回旧树
这个就是可持久化的实现方式了,那么,可持久化和权值线段树怎么解决区间k大问题呢?
首先把值全部排序去重,用于建权值线段树,可以用stl中的sort和unique解决……权值线段树里保存的内容是值的数量,比如插入三个3就记录为3,再然后按照序列顺序依次插入节点,由于这是可持久化线段树,所以请用可持久化的方法插入……
什么,你说线段树不支持插入?你开始建棵空树啊,反正你都知道权值了,照着往下找,把插入改成单点+1不久完了吗?
然后就到了查询了,首先因为你是可持久化的,那么查询l到r区间就是你第r次插入减去第l-1次插入后的线段树的样子,想想对不对,可持久化后你总共建立了n棵线段树,两棵线段树相减,得到的就是一棵只有中间过程的线段树,在这里也就是第l次操作到第r次操作了
那么我们得到了这个区间,就可以继续找第k大了,怎么找?因为这是权值线段树,所以是排好序的~,那么就可以像平衡树一样找啦
代码么……
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int ls[5000000]; //左儿子
int rs[5000000]; //右儿子
int num[5000000]; //过会再讲
int san[5000000]; //过会再讲
int sum[5000000]; //线段树里保存的值
int T[5000000]; //每个节点在线段树里的标号
int n,m,tot = 0;
建树函数:
void build(int l,int r,int &x)
{
x = ++tot;//不再使用堆式存储,而是动态开点
sum[x] = 0;//初始是一棵空树
if(l == r)
return ;
int mid = (l+r)/2;
build(l,mid,ls[x]);
build(mid+1,r,rs[x]);
}
修改操作:
void update(int last,int p,int l,int r,int &x)//p点加1
{
x = ++tot;
ls[x] = ls[last];
rs[x] = rs[last];
sum[x] = sum[last] + 1;//首先继承之前的线段树
if(l == r)
return ;
int mid = (l+r)/2;
if(p <= mid)
update(ls[last],p,l,mid,ls[x]);
else
update(rs[last],p,mid+1,r,rs[x]);//分清情况,只往一边建
}
查询操作:
int query(int s,int t,int l,int r,int k)//查询s到t区间第k大的数
{
if(l == r)
return l;
int mid = (l+r)/2;
int cnt = sum[ls[t]] - sum[ls[s]];//cnt为左子树新树减旧树
if(k <= cnt)
return query(ls[s],ls[t],l,mid,k);
return query(rs[s],rs[t],mid+1,r,k-cnt);
}
主函数及预处理:
int main()
{
int x,y,z;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++)
{
scanf("%d",&san[i]);
num[i] = san[i];
}
sort(san+1,san+n+1);
int cnt = unique(san+1,san+n+1)-san-1;
build(1,cnt,T[0]);
for(int i = 1;i <= n;i ++)
num[i] = lower_bound(san+1,san+cnt+1,num[i]) - san;
for(int i = 1;i <= n;i ++)
update(T[i-1],num[i],1,cnt,T[i]);
for(int i = 1;i <= m;i ++)
{
scanf("%d%d%d",&x,&y,&z);
printf("%d\n",san[query(T[x-1],T[y],1,cnt,z)]);
}
return 0;
}
代码不长,比较简单,也就懒得解释了……