【基础模型】LIS 最长上升子序列

问题:
给你一个数列,让你找出最长上升子序列。(子序列大概就是说从前往后按顺序取数,可以不连续取)
例如: 1 2 5 8 9 1 2 6 7 8
最长上升子序列就是1 2 5 6 7 8 ,长度为6.

如果现在我们只想求长度,那么一个十分朴素的想法就是:
(f[i])表示以s[i]为结尾的最长上升子序列长度,那么易得(f[i] = max(f[j]))其中j满足(s[j] < s[i]).
然后我们分别枚举i和j,那么我们就得到了一个(n^2)的算法。
但如果我们需要更优秀的时间复杂度呢?
可以考虑这样一种思路:
维护一个单调上升的数组,不断的加入新的数,用更优秀的数代替不那么优的数,最后得到一个最优的长度。
那么考虑如何维护。
我们从前往后一个一个的加入新数,如果这个新数比数组最后一个还要大,那么加入到数组末尾。
否则二分查找出这个数组内最靠前的比新数大的数,然后用新数替换掉它。
最后数组长度就是最长上升子序列的长度.
我们来证明这样为什么是正确的。
如果你直接把数塞到数组末尾,这个正确性是显然的对吧。
那么我们其实只需要证明为什么把数塞到数组中间(即替换掉一个前面的数),其实是不影响后续操作的正确性的。
假设我们用新数x替换掉了第t个数,那么对于t之前的数而言,你只是在它们末尾塞了一个数,所以对它们没有影响,而对于t位置本身,如果后面的数想要接在这个数后面成为一个子序列的话,其实完全可以选择接在这个新数x后面,毕竟x比t之前的所有数都要大,因此可以取代t数在原来子序列当中的地位,而x又比t小,因此能接在t后面的数肯定也能接在x后面,而且既然是“后面的数”,那肯定不用担心顺序问题。因此对于t位置也不会有影响。
那么对于t后面的数呢?
我们可以发现,在这个数列里面,我们其实是在用一个数去代表一个子序列,每个数就代表了以它为末尾最长的那个子序列,它所在的位置其实就是这个子序列的长度。
因此对于后面来的任意一个数而言,如果我们想接到一个前面的子序列,实际上我们只需要用到那个子序列的末尾数字。因此在该数组中某个数y前面的数被修改了其实根本不会对这个数y产生影响,因为后面的数要用到y的时候,只需要用到y本身就足以获取长度和末尾信息。

小提醒:这个算法最后得到的数组并不一定是最优的最长上升子序列,因为你修改数组中间的数,虽然不会影响后期长度更新的正确性,但还是改变了这个数组的组成的。

#include<bits/stdc++.h>
using namespace std;
#define Ri register int
#define AC 110000

int n, m, ans;
int s[AC]; 

int read()
{
	int x = 0; char c = getchar();
	while(c > '9' || c < '0') c = getchar();
	while(c <= '9' && c >= '0') x = x * 10 + c - '0', c = getchar();
	return x;
}

int half(int x)//find the first one which is bigger than x
{
	int l = 1, r = m;
	if(l > r) return l;
	while(l < r)
	{
		int mid = (l + r) >> 1;
		if(s[mid] < x) l = mid + 1;
		else r = mid;
	}
	return l;
}

void work()
{
	n = read();
	for(Ri i = 1; i <= n; i ++)
	{
		int now = read();
		if(!m || s[m] < now) s[++ m] = now;
		else s[half(now)] = now;
	}
	printf("%d
", m);
	for(Ri i = 1; i <= m; i ++) printf("%d ", s[i]);
	printf("
");
}

int main()
{
	freopen("1.in", "r", stdin);
	work();
	fclose(stdin);
	return 0;
}
原文地址:https://www.cnblogs.com/ww3113306/p/13649090.html