主席树基本操作:静态区间第k大

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=1e9+,MAXN=2e5+,SIZE=MAXN*;
int N,M,K;
int tmp[MAXN],A[MAXN],rt[MAXN],order[MAXN];
int sz,lson[SIZE],rson[SIZE],sum[SIZE];
int init(int l,int r){
int cur=++sz;
if(l<r){
int mid=(l+r)>>;
lson[cur]=init(l,mid);
rson[cur]=init(mid+,r);
}
return cur;
}
int modify(int pre,int l,int r,int x){
int cur=++sz;
lson[cur]=lson[pre];
rson[cur]=rson[pre];
sum[cur]=sum[pre]+;
int mid=(l+r)>>;
if(l<r){
if(x<=mid)
lson[cur]=modify(lson[pre],l,mid,x);
else
rson[cur]=modify(rson[pre],mid+,r,x);
}
return cur;
}
int query(int x,int y,int l,int r,int k){
if(l==r)
return l;
int ii=sum[lson[y]]-sum[lson[x]],mid=(l+r)>>;
if(ii>=k){
return query(lson[x],lson[y],l,mid,k);
}else{
return query(rson[x],rson[y],mid+,r,k-ii);
}
}
int main(){
scanf("%d%d",&N,&K);
for(int i=;i<=N;i++){
scanf("%d",tmp+i);
A[i]=tmp[i];
}
sort(A+,A+N+);
int M=unique(A+,A+N+)-A-;
rt[]=init(,M);
for(int i=;i<=N;i++){
order[i]=lower_bound(A+,A+M+,tmp[i])-A;
rt[i]=modify(rt[i-],,M,order[i]);
}
while(K--){
int ii,jj,kk;
scanf("%d%d%d",&ii,&jj,&kk);
printf("%d\n",A[query(rt[ii-],rt[jj],,M,kk)]);
}
return ;
}

模板代码

题面:给一个长为n的序列,m次询问,每次询问[l, r]内第k大的数是几。 n <= 100000, m <= 5000

主席树实现:参考自bestFy (作者:bestFy,来源:CSDN )

首先我们要将所有数字离散化。主席树相当于是在每个位置维护了一个线段树,线段树的节点是一个区间[x, y],这里的x和y都是离散后数的编号。

当然如果真的在每个位置建一棵线段树,空间肯定爆炸,不过我们先不管这个问题。

主席树节点中维护的值,是1~i之间这个区间内出现了数的次数。然后当我们查询的时候,就是利用到了前缀和的思想

区间[x,y]中每个数字出现的次数即为第y棵线段树中的值-第x-1棵线段树中的值

然后我们来模拟一下一组数据吧。


首先建树。

然后我们逐一插入数字。

将每个数字的大小(即离散化后的编号)插入到它的位子上,然后并把所有包括它的区间的sum都++。

  

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

直到把所有数字插入以后,情况是这样的。

那么建树的具体过程大致如上

接下来我们考虑查询。

要查询[2, 5]中第3大的数我们首先把第1棵线段树和第5棵拿出来。

然后我们发现,将对应节点的数相减,刚刚好就是[2, 5]内某个范围内的数的个数。比如[1, 4]这个节点相减是2,就说明[2. 5]内有2个数是在1~4范围内(就是2, 3)。

所以对于一个区间[l, r],我们可以每次算出在[l, mid]范围内的数,如果数量>=k(k就是第k大),就往左子树走,否则就往右子树走。

代码实现:

我们用动态开点线段树,定义一些变量

int N:数字总数,M:离散化后数字总数,K:查询数量;
int tmp[MAXN]:记录数值大小,A[MAXN]:排序的数值,rt[MAXN]:每一个节点的线段树的根节点,order[MAXN]:每一个数字的排名;
int sz:线段树节点总数,lson[SIZE]:左儿子下标,rson[SIZE]:右儿子下标,sum[SIZE]:统计和;

建树:先建一棵空的权值线段树,所有点的值都是0

int init(int l,int r){
int cur=++sz;
if(l<r){
int mid=(l+r)>>;
lson[cur]=init(l,mid);
rson[cur]=init(mid+,r);
}
return cur;
}

插入

我们将N个数离散化,按照排名统计到N棵线段树上

for(int i=;i<=N;i++){
scanf("%d",tmp+i);
A[i]=tmp[i];
}
sort(A+,A+N+);
int M=unique(A+,A+N+)-A-;
rt[]=init(,M);
for(int i=;i<=N;i++){
order[i]=lower_bound(A+,A+M+,tmp[i])-A;
rt[i]=modify(rt[i-],,M,order[i]);
}

每次建立一棵新的线段树,但如果建一棵全新的,空间和时间都会爆炸

因为我们更新一个节点的值,只有一条logM的链上的值会改变,所以没有更改的节点都可以使用上一棵树的

每一次只要按照线段树的方式遍历,需要加的儿子则新建,否则将儿子指向上一棵树的儿子

int modify(int pre,int l,int r,int x){
int cur=++sz;
lson[cur]=lson[pre];
rson[cur]=rson[pre];
sum[cur]=sum[pre]+;
int mid=(l+r)>>;
if(l<r){
if(x<=mid)
lson[cur]=modify(lson[pre],l,mid,x);
else
rson[cur]=modify(rson[pre],mid+,r,x);
}
return cur;
}

查询:

因为第i棵线段树每个节点的值为区间[1,i]上每个数字出现的数字,所以第y棵树的每个节点的值-第x-1棵树的每个节点的值即为区间[x,y]上每一个数字出现的次数

while(K--){
int ii,jj,kk;
scanf("%d%d%d",&ii,&jj,&kk);
printf("%d\n",A[query(rt[ii-],rt[jj],,M,kk)]);
}

每一次在权值线段树上查询区间第k大:

int query(int x,int y,int l,int r,int k){
if(l==r)
return l;
int ii=sum[lson[y]]-sum[lson[x]],mid=(l+r)>>;
if(ii>=k){
return query(lson[x],lson[y],l,mid,k);
}else{
return query(rson[x],rson[y],mid+,r,k-ii);
}
}

最终代码:

 #include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=1e9+,MAXN=2e5+,SIZE=MAXN*;
int N,M,K;
int tmp[MAXN],A[MAXN],rt[MAXN],order[MAXN];
int sz,lson[SIZE],rson[SIZE],sum[SIZE];
int init(int l,int r){
int cur=++sz;
if(l<r){
int mid=(l+r)>>;
lson[cur]=init(l,mid);
rson[cur]=init(mid+,r);
}
return cur;
}
int modify(int pre,int l,int r,int x){
int cur=++sz;
lson[cur]=lson[pre];
rson[cur]=rson[pre];
sum[cur]=sum[pre]+;
int mid=(l+r)>>;
if(l<r){
if(x<=mid)
lson[cur]=modify(lson[pre],l,mid,x);
else
rson[cur]=modify(rson[pre],mid+,r,x);
}
return cur;
}
int query(int x,int y,int l,int r,int k){
if(l==r)
return l;
int ii=sum[lson[y]]-sum[lson[x]],mid=(l+r)>>;
if(ii>=k){
return query(lson[x],lson[y],l,mid,k);
}else{
return query(rson[x],rson[y],mid+,r,k-ii);
}
}
int main(){
scanf("%d%d",&N,&K);
for(int i=;i<=N;i++){
scanf("%d",tmp+i);
A[i]=tmp[i];
}
sort(A+,A+N+);
int M=unique(A+,A+N+)-A-;
rt[]=init(,M);
for(int i=;i<=N;i++){
order[i]=lower_bound(A+,A+M+,tmp[i])-A;
rt[i]=modify(rt[i-],,M,order[i]);
}
while(K--){
int ii,jj,kk;
scanf("%d%d%d",&ii,&jj,&kk);
printf("%d\n",A[query(rt[ii-],rt[jj],,M,kk)]);
}
return ;
}

可持久化线段树(主席树)——静态区间第k大的更多相关文章

  1. HDU3473--Minimum Sum(静态区间第k大)

    Minimum Sum Time Limit: 16000/8000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tota ...

  2. 主席树学习笔记(静态区间第k大)

    题目背景 这是个非常经典的主席树入门题——静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出 ...

  3. poj2104&&poj2761 (主席树&&划分树)主席树静态区间第k大模板

    K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 43315   Accepted: 14296 Ca ...

  4. 主席树(静态区间第k大)

    前言 如果要求一些数中的第k大值,怎么做? 可以先就这些数离散化,用线段树记录每个数字出现了多少次. ... 那么考虑用类似的方法来求静态区间第k大. 原理 假设现在要有一些数 我们可以对于每个数都建 ...

  5. 静态区间第k大(归并树)

    POJ 2104为例 思想: 利用归并排序的思想: 建树过程和归并排序类似,每个数列都是子树序列的合并与排序. 查询过程,如果所查询区间完全包含在当前区间中,则直接返回当前区间内小于所求数的元素个数, ...

  6. ZOJ -2112 Dynamic Rankings 主席树 待修改的区间第K大

    Dynamic Rankings 带修改的区间第K大其实就是先和静态区间第K大的操作一样.先建立一颗主席树, 然后再在树状数组的每一个节点开线段树(其实也是主席树,共用节点), 每次修改的时候都按照树 ...

  7. 主席树初步学习笔记(可持久化数组?静态区间第k大?)

    我接触 OI也快1年了,然而只写了3篇博客...(而且还是从DP跳到了主席树),不知道我这个机房吊车尾什么时候才能摸到大佬们的脚后跟orz... 前言:主席树这个东西,可以说是一种非常畸形的数据结构( ...

  8. HDU 2665 Kth number(主席树静态区间第K大)题解

    题意:问你区间第k大是谁 思路:主席树就是可持久化线段树,他是由多个历史版本的权值线段树(不是普通线段树)组成的. 具体可以看q学姐的B站视频 代码: #include<cmath> #i ...

  9. 静态区间第k大(主席树)

    POJ 2104为例(主席树入门题) 思想: 可持久化线段树,也叫作函数式线段树,也叫主席树(高大上). 可持久化数据结构(Persistent data structure):利用函数式编程的思想使 ...

随机推荐

  1. AtCoder ABC 131E Friendships

    题目链接:https://atcoder.jp/contests/abc131/tasks/abc131_e 题目大意 给定 N 和 K,要求构造有 N 个点,恰有 K 对点,它们的最短距离为 2 的 ...

  2. 拾遗:Perl 正则表达式

    三种正则模式: 匹配:m//,其中前缀 m 可省略 替换:s/// 转化:tr/// 操作符: =~:存在匹配项则返回结果 !~:不存在匹配项则返回结果 修饰符: i:忽略大小写,如:s/.../.. ...

  3. python读取Excel表格文件

    python读取Excel表格文件,例如获取这个文件的数据 python读取Excel表格文件,需要如下步骤: 1.安装Excel读取数据的库-----xlrd 直接pip install xlrd安 ...

  4. USACO2005 Cow Acrobats /// 前缀和 oj23402

    题目大意: N (1 ≤ N ≤ 50,000)头牛叠罗汉 找到最优排序使所有牛的风险值总和最小 每头牛重量 (1 ≤ Wi ≤ 10,000) 力量 (1 ≤ Si ≤ 1,000,000,000) ...

  5. Android开发 QRCode二维码开发第三方框架

    前言 Android开发里二维码开发经常用到,这里简单的介绍下Android开发里的二维码. 最广泛使用的二维码库zxing zxing是最广泛的二维码库各个平台都可以适用它,但是Android平台使 ...

  6. Navicat Premium下载、安装、破解

    Navicat Premium 是一套数据库管理工具,让你以单一程序同時连接到 MySQL.MariaDB.SQL Server.SQLite.Oracle 和 PostgreSQL 数据库. 此外, ...

  7. GCRoots 对象

    GC Roots 虚拟机栈(栈帧中的本地变量表)中引用的对象 方法区中的类静态属性引用的对象 方法区中的常量引用的对象 原生方法栈(Native Method Stack)中 JNI 中引用的对象 可 ...

  8. duilib教程之duilib入门简明教程10.界面设计器 DuiDesigner

    上一个教程讲解了怎么布局最大化.最小化.关闭按钮,但是如果手动去计算这三个按钮的位置和大小的话,非常的不直观,也很不方便.    所以这一章准备介绍duilib的UI设计器,由于这个设计器很不完善,也 ...

  9. el-upload文件上传组件

    一.介绍 element-ui的组件之一,用来点击上传文件 官方是使用 before-upload 限制用户上传的图片格式和大小.但是某些浏览器不支持此方法,所以使用on-change来代替. 二.代 ...

  10. VS2010-MFC(对话框:消息对话框)

    转自:http://www.jizhuomi.com/software/171.html 前面几节讲了属性页对话框,我们可以根据所讲内容方便的建立自己的属性页对话框.本节讲解Windows系统中最常用 ...