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;
}
posted @ 2021-07-07 23:19  dbg_8  阅读(99)  评论(0)    收藏  举报