最长递增子序列的长度「LIS」使用线段树

1. 前言

最长递增子序列是一道经典的算法问题,在计算机科学中被广泛研究和应用,它的求解方法有很多种,其中使用线段树的方法是一种比较高效的方法,可以在较短的时间内求出最长递增子序列的长度。

2. 问题描述

给定一个长度为 n 的序列 A,找到 A 的最长递增子序列,并输出该子序列的长度。

2.1 什么是递增子序列?

递增子序列是指在一个序列中,选取任意一些数构成一个新的子序列,使得新的子序列中的数按照先后次序递增。例如,序列 A={1, 3, 2, 4, 5, 3, 7} ,它的一个递增子序列是 {1, 2, 4, 5, 7}。

3. 解决方案

3.1 思路

使用线段树是一种比较高效的方法,它的思路是将子序列转化为一棵线段树,然后将线段树进行分治,可以得到较优的时间复杂度。

3.2 代码实现

const int MAXN = (int)1e5 + 5;

int n, a[MAXN], b[MAXN];

class SegmentTree {

public:

void build(int l, int r, int rt) {

lazy[rt] = 0;

if (l == r) {

mx[rt] = dp[l];

return;

}

int mid = (l + r) >> 1;

build(l, mid, rt << 1);

build(mid + 1, r, rt << 1 | 1);

mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);

}

void pushDown(int rt) {

if (!lazy[rt])

return;

mx[rt << 1] += lazy[rt];

mx[rt << 1 | 1] += lazy[rt];

lazy[rt << 1] += lazy[rt];

lazy[rt << 1 | 1] += lazy[rt];

lazy[rt] = 0;

}

void update(int l, int r, int rt, int x, int y, int k) {

if (x <= l && r <= y) {

mx[rt] += k;

lazy[rt] += k;

return;

}

pushDown(rt);

int mid = (l + r) >> 1;

if (x <= mid)

update(l, mid, rt << 1, x, y, k);

if (mid < y)

update(mid + 1, r, rt << 1 | 1, x, y, k);

mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);

}

int query(int l, int r, int rt, int x, int y) {

if (x <= l && r <= y)

return mx[rt];

pushDown(rt);

int mid = (l + r) >> 1, ret = -1;

if (x <= mid)

ret = max(ret, query(l, mid, rt << 1, x, y));

if (mid < y)

ret = max(ret, query(mid + 1, r, rt << 1 | 1, x, y));

return ret;

}

private:

int mx[MAXN << 2], lazy[MAXN << 2];

};

3.3 算法流程

算法流程如下:

读入序列。

离散化。

按逆序插入数值。

遍历离散化后的值,逐个更新线段树上的值。

输出最终结果。

3.4 详细解释

最长递增子序列是指序列中最长的一段递增子序列。题目要求我们求出最长递增子序列的长度,因此,我们需要找到一个最长的递增子序列。

为了方便描述,我们这里假设 A 的长度为 n,其元素分别为 a1,a2,...,an。

在解决这个问题之前,我们需要知道一个连续子序列的性质:如果 a[l],a[l+1],...,a[r-1],a[r] 是一个递增子序列,那么对于任意的 l' 和 r' (l ≤ l' ≤ r' ≤ r),a[l'],a[l'+1],...,a[r'-1],a[r'] 也是一个递增子序列。具有这个性质的最长子序列一定是唯一的。

为了找到最长递增子序列,我们可以设计出一种算法,依次找到长度为 1、2、3、...、n 的子序列中最长的递增子序列,最终找到的最长子序列即为最长递增子序列。

考虑使用动态规划算法来解决这个问题。设 dp[i] 表示以 a[i] 为结尾的最长递增子序列的长度。显然,dp[1]=1。

状态转移方程如下:

dp[i] = max{dp[j] + 1},其中 1≤j

通过状态转移方程,我们可以用 O(n^2) 的时间复杂度求出最长递增子序列的长度,但是这样会超时。

接下来,我们考虑如何使用线段树来优化这个算法。

假设我们已经求出 dp[1],dp[2],...,dp[i-1] 的值。注意到在求 dp[i] 的时候,dp[j] + 1 的值可能相等,而且相等的位置在一个区间内。也就是说,对于一段区间 [l,r],如果 a[j] 属于这个区间,那么 dp[j] + 1 的值也一定属于这个区间。

这样的话,我们就可以使用线段树来维护区间最大值。具体来说,每次求解 dp[i] 的时候,我们在线段树上查询区间 [1,a[i]-1] 内的最大值,然后把这个最大值加上 1。最后,判断一下 dp[i] 是否比当前最长递增子序列的长度更长,如果是,就更新最长递增子序列的长度为 dp[i]。

总共需要查询 n 次,每次查询的时间复杂度为 O(logn),因此总时间复杂度为 O(nlogn)。使用线段树优化后,可以在较短的时间内得到最长递增子序列的长度。

4. 总结

最长递增子序列是一道经典的算法问题,在计算机科学中被广泛研究和应用,使用线段树可以在较短的时间内求出最长递增子序列的长度。

线段树是一种高效的数据结构,可以用来解决许多区间处理相关的问题。在本题中,使用线段树可以将时间复杂度从 O(n^2) 优化到 O(nlogn),使得算法的效率得到了显著提升。

通过本题的学习,我们不仅了解了最长递增子序列的定义和求解方法,还深入学习了线段树的相关知识。这些知识可以广泛应用到计算机科学的许多领域中,是我们打好基础、提高能力的重要工具。