📅  最后修改于: 2023-12-03 15:36:40.338000             🧑  作者: Mango
最长上升子序列(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)$,并且实现相对简单。