题目链接: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. Java实现经理与员工的差异

    对于在同一家公司工作的经历和员工而言,两者是有很多共同点的.例如,每个月都要发工资,但是经理在完成目标任务后,还会获得奖金.此时,利用员工类来编写经理类就会少写很多代码,利用继承技术可以让经理类使用员 ...

  2. PHP mysql经典问题,防止库存把控不足问题

    在目前这家公司做的第一个项目抽奖项目,要求每人每天可以有20次抽奖机会,抽奖机会可以通过多种方式获取,那么就要求每次入库增加抽奖机会的时候检测当前拥有的抽奖机会是否达到了20次,如果达到了,就不再增加 ...

  3. Android开发-- 使用ADT23 的一些问题

    在使用最新版ADT 23进行android学习时发现一些问题: 1.通过设置intent的action来启动另外一个activity时,会出现No Activity found to handle I ...

  4. Unity3D Shader官方教程翻译(十九)----Shader语法,编写表面着色器

    Writing Surface Shaders Writing shaders that interact with lighting is complex. There are different ...

  5. flask livereload用法

    #coding=utf-8 from flask import Flask from flask_script import Manager app = Flask(__name__) manager ...

  6. js 中的break continue return

    break:跳出整个循环 1.当i=6时,就跳出了整个循环,此for循环就不继续了: continue:跳出当前循环,继续下一次循环: return :指定函数返回值 1.在js当中,常使用retur ...

  7. C++标准程序库笔记之一

    本篇博客笔记顺序大体按照<C++标准程序库(第1版)>各章节顺序编排. ---------------------------------------------------------- ...

  8. 关于丢失或者损坏/etc/fstab文件后的一些探讨

    1.模仿,假设不小心删除了/etc/fstab文件:大家都知道,Linux系统启动的时候会读取该文件来挂载分区,如果缺失该文件,系统一般不能正常启动. 2.采用reboot命令或者alt+ctrl+d ...

  9. pom.xml文件错误

    刚创建的maven项目,马上pom.xml的第一行就报错这是第一行:<project xmlns="http://maven.apache.org/POM/4.0.0" xm ...

  10. php pear包打包方法

    一)首先下载工具onion 浏览器打开,服务器上wget测试无法正常下载 地址:https://raw.github.com/c9s/Onion/master/onion 二)在临时目录下,建立相关目 ...