DP-最长上升子序列(LIS)
引入
给定数列 \(a_1, a_2, ... a_n\), 求数列 \(a\) 中子序列 \(b\) 长度 \(m\) 最长, \(b\) 满足 \(b_1 < b_2 < ... < b_m\). 这个序列被称为最长上升子序列(LIS).
要求时间复杂度为 O(nlogn).
算法
n^2 的算法很容易实现, 这里不再阐述.
举例说明算法, 给定数列如下
pos 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
a 10 20 30 40 50 11 12 13 14 15 16 19 17 18 19 20
在此,增加数组 dis 表示长度为 i 的 LIS 结尾元素的最小值. 可以发现, dis[i] 的值越小, 越有利于在当前 LIS 后添加元素. 我们可以枚举 a 中的每个元素(O(n)), 看能不能将当前元素添加在 LIS 后或将 LIS 的某位元素替换为当前元素. 添加成立的条件是当前元素的值大于 LIS 末位元素的值; 替换成立的条件是 LIS 的某位元素是第一个大于或等于当前元素的元素.
进行替换判断时, 你可以用二分查找所替换的元素 (因为 dis 是单调的), 也可以使用函数 lower_bound() 或树状数组等方法.
代码(AT2827)
#include <stdio.h>
#include <string.h>
int n;
int a[100003], dis[100003];
int main() {
memset(dis, 0x3F, sizeof(dis)); // 初始化, dis 应为极大值
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int ans = 1;
dis[1] = a[1];
for (int i = 2; i <= n; i++) {
if (a[i] > dis[ans]) // 在当前 LIS 后添加当前元素
dis[++ans] = a[i];
else { // 在 dis 中找到第一个 >= a[i] 的位置并用当前元素替换
int l = 1, r = ans;
while (l < r) { // 使用二分查找, 这里可以用 lower_bound() 替换, 也可以用树状数组等其他方法进行查找
int mid = (l + r) / 2;
if (dis[mid] >= a[i])
r = mid;
else
l = mid + 1;
}
dis[l] = a[i];
}
}
printf("%d\n", ans);
return 0;
}
( ゚∀゚)o彡゜ ヒーコー ヒーコー!

浙公网安备 33010602011771号