【做练习】K-th Number(线段树)线段树的原理

1. 题目

描述

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.

输入

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).

输出

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

样例输入

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

样例输出

5
6
3

翻译一下

简单说,就是输入一个整数数组,每个整数都互相不同。然后执行若干次查询操作。每次查询任意一个区间中第k大的数字。


2. 分析

因为是“任意区间”,这个题可以用线段树来做。我们为线段树的每个节点,维护其区间已经排好序的数组元素,这样就能极大地方便查询。而建树过程可以用归并排序解决。

也可以用更高级一点的主席树速度会更快,但在这里我正好复习到线段树,所以用线段树。

2.1. 线段树

2.1.1 介绍

线段树是一种二叉树,用于处理区间修改、查询问题。我们经常拿它和树状数组比较,它比树状数组更加灵活,但消耗的空间代价会比较大一些。

设有一个数组 A [ N ] A[N] A[N],对于它的任意一个区间 A [ i : j ] A[i:j] A[i:j],都存在一个属性 P [ i : j ] P[i:j] P[i:j],我们希望频繁地查询、修改某一区间属性,不妨为它建立一棵线段树。

线段树的每个节点都为一个区间。子节点对应着子区间,兄弟节点是父节点区间的一个分割。我们以区间 [ L , R ] [L,R] [L,R]为例,如果 L = R L=R L=R,这其实是一个标量,作为叶节点;如果 L < R L<R L<R,则它被分为两个子区间: [ L , M ] [L, M] [L,M] [ M + 1 , R ] [M+1, R] [M+1,R],其中 M = ( L + R ) / 2 M=(L+R)/2 M=(L+R)/2
示例如下:
在这里插入图片描述

2.1.2 存储方式

一个链式存储的线段树节点为:

struct node
{
   
	int left, right; // 区间的范围,闭区间
	node* leftSon, rightSon; // 两个子节点
	/* data */
};

以上,使用指针来组织树结构,这种方式比较直观好理解。

不过,线段树作为二叉树我们可以使用线性存储方式,这种方式代码量会比较小,程序会比较简单。

struct node
{
   
	int left, right; // 区间的范围。闭区间
	/* data */
} nodes[4*N];

其中, nodes[0]代表根节点。序号为i的节点,它的左子序号为2*i+1,而它的右子序号为2*i+2
那么,问题来了,使用线性存储,为长度L的总区间建立线段树,需要分配多大的节点数组呢?答案是:最小的大于L的2的整数幂,再乘以2。如果你觉得这样太复杂,在空间允许的前提下,直接分配4*L是绝对够了。

2.1.3 如何建树?

通常使用递归方法建立树。
以下是一个示例(基于线性存储):

node nodes = nodes[4000000];
void BuildTree(int i, int left, int right)
{
   
	nodes[i].left = left;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值