题目链接: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. Linq To EF (添加记录后获取添加的自增ID和叫“ID”的列不是自增列不让插入的问题)

    1:添加记录后,如何获取新添加的ID的值 比如,一个实体 TestEntity   对应一个表TestEntity(ID主键自增,Name,age),使用linq to ef   添加一条记录后,如何 ...

  2. GCD (Grand Central Dispatch) 笔记

    GCD (Grand Central Dispatch) 是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式. 在Mac OS X 10.6和IOS 4.0之后开 ...

  3. Tree Recovery(前序中序求后序)

    Tree Recovery Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 14640   Accepted: 9091 De ...

  4. 一致性Hash算法原理及C#代码实现

    一.一致性Hash算法原理 基本概念 一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希空间环如下: 整个空间按顺 ...

  5. 6.824 Lab 5: Caching Extents

    Introduction In this lab you will modify YFS to cache extents, reducing the load on the extent serve ...

  6. 04python while循环语句

    使用while ture语法 luck_num = 33 flag = True while flag: guess_num = input('请输入您猜测的年龄:') if guess_num &l ...

  7. PHP代码审计笔记--代码执行漏洞

    漏洞形成原因:客户端提交的参数,未经任何过滤,传入可以执行代码的函数,造成代码执行漏洞. 常见代码注射函数: 如:eval.preg_replace+/e.assert.call_user_func. ...

  8. windows恶意软件删除工具(MRT.exe)检查计算机是否感染病毒使用图解

    Microsoft Windows恶意软件删除工具可以检查运行 Windows XP.Windows 2000 和 Windows Server 2003 的计算机是否受到特殊.流行的恶意软件(包括 ...

  9. RF-获取上个月份

    验证1: case ${NowDate} set variable 20170103 ${year} set variable ${NowDate[0:4]} ${month} set variabl ...

  10. Django SimpleCMDB 项目

    创建 SimpleCMDB 项目: [root@localhost ~]$ django-admin.py startproject SimpleCMDB 创建应用,收集主机信息: [root@loc ...