【填坑】可持久化线段树解决无修改的区间k大问题

来源:互联网 发布:手机听小说软件 编辑:程序博客网 时间:2024/05/16 23:49

区间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;}

代码不长,比较简单,也就懒得解释了……

3 0
原创粉丝点击