题目链接:http://poj.org/problem?id=2104

Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment. 
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 first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000). 
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

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

Sample Input

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output

5
6
3

Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

题意:

写一个数据结构,能够快速返回指定区间内的第k小元素。

即:数组a[1:n]包含n个不同的整数,Q(i,j,k)代表询问:“若a[i:j]中元素从小到大排列,则其中第k个元素是什么?”

例如:数组a = (1, 5, 2, 6, 3, 7, 4),现有查询Q(2, 5, 3),由于a[2:5]为(5, 2, 6, 3),升序排列后为(2, 3, 5, 6),第3个元素为5,所以Q(2, 5, 3) = 5

数据范围:

数组内元素个数n:1 ≤ n ≤ 100 000;

查询个数m:1 ≤ m ≤ 5 000;

元素大小:[-1e9,1e9];

对于询问Q(i,j,k):1 ≤ i ≤ j ≤ n, 1 ≤ k ≤ j - i + 1;

题解:


主席树参考:

  https://www.bilibili.com/video/av4619406/

  https://blog.csdn.net/regina8023/article/details/41910615

通俗易懂地讲解一下:

对于一个数字串 $s$,假设其长度为 $n$,我们考虑枚举其前缀子串:

对于第 $i(0 \le i \le n)$ 个前缀子串(即前 $i$ 个数),我们开一个 $cnt[...]$ 数组,用来记录这个子串里,每个数出现了几次。

例如:

$s = (1,3,4,3,5,1,5)$,此时 $n = 7$,考虑其:

第 $0$ 个前缀子串:$pre_0 = ()$ 为空,此时 $cnt[...]$ 数组的值全为 $0$;

第 $1$ 个前缀子串:$pre_1 = (1)$,此时 $cnt[1] = 1$,其他任意的 $cnt[i] = 0$,即仅有数字 $1$ 出现了一次;

第 $2$ 个前缀子串:$pre_1 = (1,3)$,此时 $cnt[1] = 1,cnt[3] = 1$,其他任意的 $cnt[i] = 0$,即数字 $1$ 出现了一次,数字 $3$ 出现了一次;

以此类推直到第 $7$ 个前缀子串:$pre_7 = s = (1,3,4,2,5,8,6)$,此时 $cnt[1] = 2,cnt[3] = 2,cnt[4] = 1,cnt[5] = 2$。

是不是很简单?

然后,我们不妨将cnt数组看做一条线段(这条线段的长度应当为 $O(n)$,考虑最坏情况,$s$ 内的每个数字均不重复出现,那么离散化后值域就应当是 $1 \sim n$),然后我们用线段树来维护这条线段,

然后你就想着,诶不对啊,这不行啊,原来的cnt数组,如果想要存储所有前缀子串的值不丢失的话,就已经是 $n \times n$ 的大小了,现在你还不够还要开 $n$ 个维护长度为 $n$ 的线段的线段树?

岂不是内存原地爆炸?

确实是的,这样存当然太大了,但是,我们很容易就能发现减小内存消耗的方式:

从第 $i$ 个前缀子串到第 $i+1$ 个前缀子串,我实际上只有一个点的cnt值加了 $1$,其他的点的cnt值都是不变的,而这一个点所产生的变化会一直向上传递影响直到树根节点,所以我们可以仅仅新建并存储一条路径,而非一整棵树。

解决了存储问题,我们就可以心安理得地假装:我现在已经有了 $n$ 棵维护长度为 $n$ 的线段的线段树了!

可是这样做有啥子好处呢?

也很简单,我们不妨回到最初的起点:我们就是想查询:数字串 $s$ 中任意区间内每个数字出现的次数。

可以想见,如果使用纯暴力做法,对于任意的查询 $[l,r]$,最坏肯定是 $O(n)$ 的时间复杂度才能得到答案,这太慢了,无法接受。

然后我们再来瞧瞧我们的前缀子串配合cnt数组能不能发挥点作用?

我们不妨假设第 $i$ 个前缀子串的cnt数组是:$cnt[i][...]$,也就是说,第 $i$ 个前缀子串中数字 $num$ 出现的次数为 $cnt[i][num]$

那我们就能 $O(1)$ 查询区间 $[l,r]$ 内某个数字 $num$ 出现的次数:$cnt[r][num]-cnt[l-1][num]$,

现在加大难度,如果我现在想知道区间 $[l,r]$ 内连续若干个数字 $num,num+1,num+2,...,num+p$ 的出现次数和呢?

难道要 $(cnt[r][num]-cnt[l-1][num]) + (cnt[r][num+1]-cnt[l-1][num+1]) + \cdots + (cnt[r][num+p]-cnt[l-1][num+p])$ 吗?这样最坏情况取 $p=n$ 的话又要 $O(n)$ 了,无法接受。

这就是我们要更进一步,使用线段树维护cnt数组的原因,

先将上式变形:

$\begin{array}{l} (cnt[r][num] - cnt[l - 1][num]) + (cnt[r][num + 1] - cnt[l - 1][num + 1]) + \cdots + (cnt[r][num + p] - cnt[l - 1][num + p]) \\ = (cnt[r][num] + cnt[r][num + 1] + \cdots + cnt[r][num + p]) - (cnt[l - 1][num] + cnt[l - 1][num + 1] + \cdots + cnt[l - 1][num + p]) \\ \end{array}$

不难发现:

$cnt[r][num] + cnt[r][num + 1] + \cdots + cnt[r][num + p]$ 对应的就是第 $r$ 棵线段树上区间 $[num,num+p]$ 的值;

$cnt[l - 1][num] + cnt[l - 1][num + 1] + \cdots + cnt[l - 1][num + p]$ 对应的就是第 $l-1$ 棵线段树上区间  $[num,num+p]$ 的值;

这不就变成 $O(\log n)$ 查询了嘛!舒服!

所以最基础的主席树,解决的无非就是一个问题:

对于一个数字串 $s$,我可以 $O(\log n)$ 查询任意区间 $[l,r]$ 内任意连续若干个数字 $num,num+1,num+2,...,num+p$ 的出现次数之和。


回归到本题,给出的数字序列是一个没有重复的整数序列 $s$,长度为 $n$,求任意区间内第 k 小元素,

为了方便描述,不妨先用离散化处理该整数序列,得到离散化后值域应当为 $[1,n]$,那么原数字序列就变成 $(1,2,3,...,n)$ 的某一个排列,

那么就可以用主席树,对查询函数稍作修改后,就能 $O(\log n)$ 查询:

任意区间 $[l,r]$ 内,我给定出现次数 $k$,我可以返回一个数 $p$,$p$ 满足数字 $1,2,...,p$ 在的区间 $[l,r]$ 内出现次数之和等于 $k$。

AC代码:

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=+; //主席树
struct Node{
int l,r,sum;
}node[maxn*];
int root[maxn];
int cnt;
void update(int l,int r,int x,int &y,int pos)
{
cnt++;
node[cnt]=node[x];
node[cnt].sum++;
y=cnt; if(l==r) return; int mid=(l+r)/;
if(mid>=pos) update(l,mid,node[x].l,node[y].l,pos);
else update(mid+,r,node[x].r,node[y].r,pos);
}
int query(int l,int r,int x,int y,int k)
{
if(l==r) return l; int mid=(l+r)/;
int sum=node[node[y].l].sum-node[node[x].l].sum;
if(sum>=k) return query(l,mid,node[x].l,node[y].l,k);
else return query(mid+,r,node[x].r,node[y].r,k-sum);
} //离散化
vector<int> v;
inline int getID(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+;}
inline int getVal(int id){return v.at(id-);} int a[maxn];
int main()
{
int n,q;
scanf("%d%d",&n,&q); v.clear();
for(int i=;i<=n;i++)
{
scanf("%d",&a[i]);
v.push_back(a[i]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end()); root[]=; node[].l=node[].r=; node[].sum=; //初始化第0棵树
for(int i=;i<=n;i++) update(,n,root[i-],root[i],getID(a[i])); //构建第1~n棵树 for(int i=;i<=q;i++)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",getVal(query(,n,root[l-],root[r],k)));
}
}

复杂度:

空间复杂度:

  第0棵树$O\left( n \right)$(其实实际代码写的时候是$O\left( 1 \right)$),第1~n棵树每次需要$O\left( {\log n} \right)$,总的就是$O\left( {n\log n} \right)$。

时间复杂度:

  建树$O\left( {n\log n} \right)$。

  一次查询$O\left( {\log n} \right)$。

注:

vector容器的方法:

  end()  指向迭代器中末端元素的下一个,指向一个不存在元素。
  erase(pos)  删除pos位置的数据,传回下一个数据的位置。
  erase(st,ed)  删除[st,ed)区间的数据,传回下一个数据的位置。

unique函数:

  unique(st,ed)  对[st,ed)区间的数据进行去重。

  功能:对有序的容器重新排列,将所有第一次出现的元素从前往后排,所有重复出现的元素依次排在后面。

  返回值:返回迭代器,迭代器指向的是重复元素的首地址。

lower_bound(st,ed,x)  在有序的序列中返回[st,ed)区间内第一个不小于x的元素的位置。

upper_bound(st,ed,x)  在有序的序列中返回[st,ed)区间内第一个大于x的元素的位置。

SPOJ MKTHNUM & POJ 2104 - K-th Number - [主席树模板题]的更多相关文章

  1. 【POJ 2104】 K-th Number 主席树模板题

    达神主席树讲解传送门:http://blog.csdn.net/dad3zz/article/details/50638026 2016-02-23:真的是模板题诶,主席树模板水过.今天新校网不好,没 ...

  2. 主席树:POJ2104 K-th Number (主席树模板题)

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

  3. POJ 2104 K-th Number(主席树模板题)

    http://poj.org/problem?id=2104 题意:求区间$[l,r]$的第k小. 思路:主席树不好理解啊,简单叙述一下吧. 主席树就是由多棵线段树组成的,对于数组$a[1,2...n ...

  4. 【BZOJ 1901】【Zju 2112】 Dynamic Rankings 动态K值 树状数组套主席树模板题

    达神题解传送门:http://blog.csdn.net/dad3zz/article/details/50638360 说一下我对这个模板的理解: 看到这个方法很容易不知所措,因为动态K值需要套树状 ...

  5. poj2104 主席树模板题

    题意 给出n个数字组成的数字序列,有m组询问.每次询问包含三个数字l,r,k.对于每个询问输出序列区间[l,r]中第k大的数字. 分析 这是主席树的模板题,套板子就可以 #include <cs ...

  6. hdu2665(主席树模板题)

    hdu2665 题意 求区间第 k 小. 分析 参考 这类题目做法挺多的,例如 划分树. 这里使用主席树再写一发,不得不说主席树相比而言要好写的多,比起普通线段树,主席树就是复用了线段树共有的信息. ...

  7. POJ-2104-K-th Number(区间第K大+主席树模板题)

    Description You are working for Macrohard company in data structures department. After failing your ...

  8. POJ 2104 主席树模板题

    #include <iostream> #include <cstdio> #include <algorithm> int const maxn = 200010 ...

  9. POJ2104 K-th Number 划分树 模板题啊

    /*Source Code Problem: 2104 User: 96655 Memory: 14808K Time: 1282MS Language: G++ Result: Accepted S ...

随机推荐

  1. DotNetBar如何控制窗体样式

    在C#中使用控件DevComponents.DotNetBar时,如何创建一个漂亮的窗口,并控制窗体样式呢?   1.新建一个DotNetBar窗体             2.使OFFICE窗口风格 ...

  2. Spring中可以复用的工具类&特性记录

    Spring 里有用工具类: GenericTypeResolver 解析泛型类型.核心逻辑还是调用 ResolvableTypeResolvableType 解析泛型类型 BeanWrapper 利 ...

  3. Ubuntu Git安装与使用

    本系列文章由 @yhl_leo 出品.转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/50760140 本文整理和归纳了关于Ub ...

  4. Kafka manager安装 (支持0.10以后版本consumer)

    下载地址:  https://pan.baidu.com/s/1jIE3YL4 步骤: 1. 解压kafka-manager-1.3.2.1.zip 2. cd kafka-manager-1.3.2 ...

  5. oracle URL参数获取

    改函数主要是从URL中获取参数例如 sssss.html?cur=aaa&ref=cccc 调用方式:f_querystr(url,'cur','&'); CREATE OR REPL ...

  6. iOS - UITextView在调用textViewDidChange方法,九宫格相关中文输入的问题

    问题一 iOS textView在调用 UITextViewDelegate 的 textViewDidChange方法,九宫格相关中文输入的问题 有时候,需要在textViewDidChange处理 ...

  7. SeaJS之use函数

    有了 define 等模块定义规范的实现,我们可以开发出很多模块.但光有一堆模块不管用,我们还得让它们能跑起来.在 SeaJS 里,要启动模块系统很简单: <script src=”path/t ...

  8. 【WEB前端开发最佳实践系列】CSS篇

    一.有效组织CSS代码 规划组织CSS代码:组织CSS代码文件,所有的CSS都可以分为2类,通用类和业务类.代码的组织应该把通用类和业务类的代码放在不同的目录中. 模块内部的另一样式规则:样式声明的顺 ...

  9. 题目1439:Least Common Multiple(求m个正数的最小公倍数lcm)

    题目链接:http://ac.jobdu.com/problem.php?pid=1439 详解链接:https://github.com/zpfbuaa/JobduInCPlusPlus 参考代码: ...

  10. 【NOI2015】荷马史诗[Huffman树+贪心]

    #130. [NOI2015]荷马史诗 统计 描述 提交 自定义测试 追逐影子的人,自己就是影子. ——荷马 Allison 最近迷上了文学.她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读 ...