POJ 2104 K-th number 主席树 函数式线段树
来源:互联网 发布:隔墙听音器专卖店淘宝 编辑:程序博客网 时间:2024/05/22 13:12
Description
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?"
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.
Input
The second line contains n different integer numbers not exceeding 109 by their absolute values --- the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).
Output
Sample Input
7 31 5 2 6 3 7 42 5 34 4 11 7 3
Sample Output
563
Hint
题意很简单,就是求一个数组里区间L~R的第K小的数。
因为最近看到一道主席树套树状数组的结合题,正好最近也还做了一些线段树的题,就想着把主席树给看一看
我大概解释一下主席树的发明的想法........以下是自己的理解
首先不考虑区间的问题,就考虑整个数组上的第k小数怎么解决,虽然说有线性时间选择的算法,但是现在要考虑用线段树来解决这个问题
用线段树是这么做的,把数组上出现的数字离散化,无妨说离散化到一个新数组dis,那么线段树就是基于这个数组,线段树维护的值就是dis的某个区间内的数字在原数组出现的总次数。
举个例子,原数组是2 3 4 5 6 7 8 9,离散化后的dis是1 2 3 4 5 6 7 8,那么这一棵线段树的根节点的值就是8(代表dis上区间1~8里的数在原数组中一共出现了8次),其左子的值是4(代表dis上区间1~4内的数在原数组出现了4次),右儿子也是4(代表dis上区间5~8内的数在原数组出现了4次)........
在具体查询第k小的数的时候,假若k的值要小于等于左儿子的值,那么就代表最后的答案在左儿子维护的区间里,反之,就在右儿子的区间里面,最后走到叶节点时就是第k小。
现在考虑带有区间的情况,这里又有另外一个性质,我们可以发现,对于我们刚刚说的线段树,dis上的区间L1~R1在原数组L2~R2上的出现次数等于dis上的区间L1~R1在原数组1~R2的出现次数减去在原数组1~L2-1上的出现次数,实际上就是前缀和的性质,只要我们构造出了前缀和,我们可以得到dis上的区间L1~R1在原数组在任意区间上的出现次数。
基于这个想法,我们可以对于每一个区间1~L建立一棵线段树,然后在查询某个区间的时候,用后面的线段树上的值减去前面线段树上的值就是dis数组在该区间上出现的真实次数。
但是在实现的时候还要考虑其他的问题,那就是这样直接建造的空间复杂度是n^2的,过于庞大。实际上我们可以发现,对于区间1~L和区间1~L+1的线段树,也就是前后相邻的线段树,它们有很大一部分是一样的,具体的说,从根节点到某个叶子节点这一条链是不一样的,其余的都是一样的,因为前后只相差1,插入的时候只修改了一条链嘛。
那么我们只构造这一条链,其余的指向原线段树就好。
这就是主席树。
老实说还有人叫它函数式线段树,我很想让人讲讲,为什么叫函数式线段树.......
以下是代码,把查询和插入都写成迭代的写法了,查询的递归写法注释掉了。
#include <iostream>#include <cstring>#include <algorithm>#include <vector>#define mid ((le+ri)>>1)using namespace std;const int maxm=1E5+10,maxn=maxm*20;vector<int> dis;int n,m,x,y,k,sz,node[maxm],arr[maxm],st[maxn],ls[maxn],rs[maxn],_ins(int y,int p,int d),query(int le=1,int ri=dis.size());inline int getid(int x){return lower_bound(dis.begin(),dis.end(),x)-dis.begin()+1;}int main(){ ios_base::sync_with_stdio(0); while(cin>>n>>m){ for(int i=1;i<=n;++i) cin>>arr[i],dis.push_back(arr[i]); sort(dis.begin(),dis.end()); dis.erase(unique(dis.begin(),dis.end()),dis.end()); for(int i=1;i<=n;++i) node[i]=_ins(node[i-1],getid(arr[i]),1); while(m--){ cin>>x>>y>>k; x=node[--x],y=node[y]; cout<<dis[query()-1]<<endl; } dis.clear(); sz=0; } return 0;}int _ins(int y,int p,int d){ int x=++sz,rt=x,le=1,ri=dis.size(); st[x]=st[y]+d; while(le<ri){ if(p<=mid){ ls[x]=++sz,rs[x]=rs[y]; x=ls[x],y=ls[y],ri=mid; }else{ rs[x]=++sz,ls[x]=ls[y]; x=rs[x],y=rs[y],le=mid+1; } st[x]=st[y]+d; } return rt;}int query(int le,int ri){ if(le==ri) return le; int num;//=st[ls[y]]-st[ls[x]]; while(le<ri){ num=st[ls[y]]-st[ls[x]]; if(num>=k) x=ls[x],y=ls[y],ri=mid; else x=rs[x],y=rs[y],k-=num,le=mid+1; } return le;// if(num>=k){// x=ls[x],y=ls[y];// return query(le,mid);// }else{// x=rs[x],y=rs[y],k-=num;// return query(mid+1,ri);// }}
PS:
我们可以发现,在这个过程中,仅仅有查询,是没有修改的,那假若有修改怎么办呢?
举个例子,给定数组,查询第K次操作前的区间和,单点更新
那么我们就可以按照时间(操作次数)来建立线段树,我们可以发现,按照时间来建立的前后相邻的线段树也只有一条链的不同而已,和上面是一样的。
只要理解的话,就可以很好的迁移了。
- poj-2104 K-th Number[主席树/函数式线段树/可持久化线段树]
- POJ 2104 K-th number 主席树 函数式线段树
- [POJ 2104]K-th Number 主席树
- poj 2104 K-th Number (主席树)
- poj 2104 K-th Number【主席树】
- POJ 2104 K-th Number [主席树]
- POJ 2104 K-th Number 主席树
- 主席树 poj 2104 K-th Number
- POJ 2104 K-th Number 主席树
- POJ 2104 K-th Number(主席树)
- 【POJ 2104 K-th Number】+ 主席树
- 【POJ】2104 K-th Number 主席树
- POJ-2104:K-th Number(主席树)
- poj 2104 K-th Number 函数式线段树
- poj 2104 K-th Number(函数式线段树)
- POJ-2104-K-th Number(函数式线段树)
- [POJ]2104 K-th Number 主席树&线段树合并&整体二分
- 主席树(可持久化线段树)讲解 [POJ 2104] K-th Number
- 重入锁
- 算法导论 练习题 16.2-6
- 【数据结构】尾插法建立单链表 以及删除操作
- Tensorflow 入门
- win10下安装ubuntu16.04双系统
- POJ 2104 K-th number 主席树 函数式线段树
- hibernate入门之oracle连接
- 史上最难的一道Java面试题
- Markdown 常用技巧
- 操作系统刷题(十二)
- 正三角形的外接圆面积
- 4种JavaScript的内存泄露及避免方法
- 技术博客1
- 前端插件datatable中取消默认排序的图标