📜  使用段树的LIS(1)

📅  最后修改于: 2023-12-03 15:36:40.338000             🧑  作者: Mango

使用段树的最长上升子序列(LIS)

什么是最长上升子序列(LIS)

最长上升子序列(LIS) 是一个在给定的序列中找到一个最长的子序列,使得这个子序列中所有元素的值都是严格递增的。

例如,序列 [1,3,2,4,6,5] 的最长上升子序列是 [1,2,4,6]。

如何使用段树求解最长上升子序列

使用动态规划算法可以在 O(n^2) 时间内求解最长上升子序列,但是使用段树可以将时间复杂度降低到 O(nlogn)。

状态定义

假设 $dp[i]$ 表示以第 $i$ 个数结尾的最长上升子序列长度,那么最终答案即为 $\max dp[i]$。

转移方程

对于每个 $i$,我们需要枚举在 $i$ 之前所有比 $a_i$ 小的数的状态,然后加上当前的数 $a_i$,得到以 $a_i$ 结尾的最长上升子序列长度。即:

$$dp[i] = \max{dp[j] + 1 \space | \space j < i \space \mathrm{and} \space a_j < a_i}$$

段树优化

使用普通的动态规划算法求解 LIS 时间复杂度为 $O(n^2)$,需要使用段树优化。

我们可以将转移方程中的 $\max$ 用线段树来维护,用二分查找的方式找到符合条件的 $j$,然后将 $dp[i]$ 更新为 $dp[j] + 1$。

代码实现

下面是使用 Python 语言实现使用段树的最长上升子序列的代码片段:

from bisect import bisect_left
from typing import List

class SegmentTree:
    def __init__(self, size: int):
        self.size = size
        self.tree = [0] * (2 * size)

    def update(self, i: int, v: int) -> None:
        i += self.size
        self.tree[i] = v
        while i > 1:
            i //= 2
            self.tree[i] = max(self.tree[2*i], self.tree[2*i+1])

    def query(self, a: int, b: int) -> int:
        res = 0
        l = a + self.size
        r = b + self.size
        while l < r:
            if l % 2 == 1:
                res = max(res, self.tree[l])
                l += 1
            if r % 2 == 1:
                r -= 1
                res = max(res, self.tree[r])
            l //= 2
            r //= 2
        return res

def lis(nums: List[int]) -> int:
    n = len(nums)
    dp = [0] * n
    seg_tree = SegmentTree(n)
    for i in range(n):
        dp[i] = seg_tree.query(0, bisect_left([seg_tree.tree[j] for j in range(seg_tree.size, 2*seg_tree.size)], nums[i]))
        seg_tree.update(i, dp[i] + 1)
    return max(dp)

其中,SegmentTree 类是实现线段树的代码,lis 函数使用段树实现求解最长上升子序列的代码。这里使用 bisect_left 函数实现二分查找。

总结

使用段树实现最长上升子序列算法可以将时间复杂度降低到 $O(nlogn)$,并且实现相对简单。