zoukankan      html  css  js  c++  java
  • 【基础模型】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;
    }
    
  • 相关阅读:
    考试题1
    九九乘法表
    面向对象
    *****用循环运用
    编辑实现逻辑运算和循环计数
    求:1-3+5-7+...-99+101的值的(分析求解)。
    三层for循环求解组成三角形边的组合
    for循环的应用
    Java基础输出语句
    习题4 编写一个方法method(),判断一个数能否同时被3和5整除
  • 原文地址:https://www.cnblogs.com/ww3113306/p/13649090.html
Copyright © 2011-2022 走看看