动规讲解基础讲解七——最长单增子序列

(LIS Longest Increasing Subsequence)给定一个数列,从中删掉任意若干项剩余的序列叫做它的一个子序列,求它的最长的子序列,满足子序列中的元素是单调递增的。


例如给定序列{1,6,3,5,4},答案是3,因为{1,3,4}和{1,3,5}就是长度最长的两个单增子序列。

处看此题,怎么做? 万能的枚举?枚举全部2^n个子序列,找出最长的,固然可以,就是复杂度太高。我们为什么要枚举呢?因为要知道取了哪些数,其实我们只需要考虑上一个数和取了几个数就可以了吧?因为单增的意思是比前一个数大,我们要加入这个数的时候,只考虑它比之前加入的最后一个数大就可以了。而最长的意思是数的个数最多,我们只要知道数的总个数就可以了,没必要知道具体有哪些数。

让我们尝试一下用动态规划的思考办法。首先设置数列是a1, a2, a3…an,为了方便我们加入一项a0=-∞,后面我们将发现这会给我们带来极大的方便。int f[i]表示以第i个数结尾的最长单调子序列的长度, 那么我们看一下加入ai之前的最后一个数是aj,显然j < i并且aj < ai,我们有f(i) = f(j) + 1,因为往后面延长了一项嘛。那根据这个式子,我们显然应该选择最大的f(j),才能让f(i)最大。

于是我们有了递推关系f(i) = max{f(j)| j < i并且aj < ai} + 1,光有了递推关系还不够,初值呢? f(0) = 0,并且我们加入了a0=-∞,这样对每个i > 0,j总是存在的,大不了就达到下标0了嘛。

伪代码:

f[0] = 0;
for i = 1 to n do
    f[i] = 0;
    for j = 0 to i – 1 do
        f[i] = max(f[i], f[j] + 1)
    endfor
endfor

显然这个算法的时间复杂度是O(n^2),空间复杂度是O(n)。老生常谈的问题,如何找到这样一个最长的子序列?记录决策的办法总是可以的我们记录一下使得f(i)最大的j。最终结果是max{f(i)},我们从这个i值一项一项不断找到前面的j即可……

更好的算法?

事实上这个题有时间复杂度更低的算法。仍然以{1,6,3,5,4}为例子,我们想像考虑5的时候,之前有两个长度为2的子序列{1,6}和{1,3},那么哪个更“好”呢?显然后者更好,因为3比6小,以3结尾的序列更容易在后面接上一个数。那么我们记录到第i个数之前每个长度的单调子序列中“最好”的那个的最后一个数的大小,考虑把当前这个数接在哪里就好了。事实上,我们的意思是在每个长度的单调子序列中选一个代表,这个代表就是其中“最好”的那个(让最后一项尽可能小),而我们可以归纳的证明,不同长度的“最好”单调子序列的最后一项是随着长度而单调递增的。这是因为,我们每次都试图把一个数加到它能接的那个最长的子序列后面,其实也是加到了它能加的末尾最大的子序列上。

那么问题明了了,开始我们只有一个长度为0的单调子序列,末尾大小认为是-∞。假设目前我们记录了f[0],f[1],f[2]…f[m]表示目前单调子序列的最长长度是m,我们考虑ai接到哪里,我们要找到小于ai的最大那一项,我们把它接到那个序列的后面。因为f是单调递增的,换句话说,我们找到x= max{x|f[x] < ai}, 把ai接到f[x]后面,得到f[x + 1] = ai,注意这样的x一定存在,因为f[0] = -∞。如果我们找到的x < m,则我们实际上更新了长度为(x + 1)的子序列的最后一项,因为显然有f[x + 1] >=  ai,我们把ai换过去,至少不会变差,这也正式我们保存每个长度“最好”的单调子序列的初衷。如果x == m,则实际上我们把子序列的长度(种类数)扩展到了(m + 1)。

最终结果是什么呢?是f那个列表的长度,也就是最终变化后的m值。

如果我们循环一个一个地看,这里就有O(m)的时间复杂度,但是因为有单调性的存在,我们可以利用二分查找算法来找到这样的x,所以这里时间复杂度是O(logm),因为m<=n,我们这里可以认为每次找到x的时间复杂度是O(logn),那么对于每个ai我们都如此做的时间复杂度就是O(nlogn)了。

我们得到了一个更快的算法。请思考如何找到具体一个子序列?(提示:仍然是“记录”决策,在找到x的时候记录就可以了。)

题解:

#include<cstdio>
#include<cstring>
using namespace std;
int n,a[1000001],f[1000001],maxn;
int main()
{
    memset(f,-127,sizeof(f));
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        if(a[i]>f[maxn]) f[++maxn]=a[i];
        else if(a[i]<f[maxn])
        {
            int l=1,r=maxn;
            while(l<=r)
            {
                int m=(l+r)/2;
                if(a[i]<f[m]) r=m-1;
                else if(a[i]==f[m])  break;
                else l=m+1;
            }
            if(f[l]>a[i]) f[l]=a[i];
        }
    }
    printf("%d",maxn);
}

如果对你有所帮助,别忘了加好评哦;么么哒!!下次见!88

时间: 2024-10-05 23:26:45

动规讲解基础讲解七——最长单增子序列的相关文章

最长单增子序列问题

问题描述:有一个长为n的数列a0,a1,a2........a(n-1).请求出这个序列中最长的单增子序列的长度.单增子序列的定义是:对于任意的 i<j,都满足ai<aj. 这个问题就是著名的最长单增子序列(LIS)问题.对于这道问题,我们可以利用动态规划来进行求解:假设dp[i]表示以a[i]为末尾的最长单增子序列的长度,则在得到dp[i]时,我们可以这样做:初始化dp[i]为1,利用一个j变量遍历已经访问过的数组a中的值,如果此时a[i]>a[j],表示我们可以在原来的子序列之后加上

动规讲解基础讲解五——最长公共子序列问题

一些概念: (1)子序列: 一个序列A = a1,a2,……an,中任意删除若干项,剩余的序列叫做A的一个子序列.也可以认为是从序列A按原顺序保留任意若干项得到的序列. 例如: 对序列 1,3,5,4,2,6,8,7来说,序列3,4,8,7 是它的一个子序列.对于一个长度为n的序列,它一共有2^n 个子序列,有(2^n – 1)个非空子序列. 请注意:子序列不是子集,它和原始序列的元素顺序是相关的. (2)公共子序列 : 顾名思义,如果序列C既是序列A的子序列,同时也是序列B的子序列,则称它为序

动规讲解基础讲解一——01背包(模板)

作为动态规划的基础,01背包的思想在许多动规问题中会经常出现,so,熟练的掌握01背包的思路是极其重要的: 有n件物品,第i件物品(I = 1,2,3…n)的价值是vi, 重量是wi,我们有一个能承重为m的背包,我们选择一些物品放入背包,显然放入背包的总重量不超过m.我们要求选择物品的总价值最大,请问如何选择?这里我们假设所有出现的数都是正整数. 第一想法是? (1) 枚举?万能的枚举啊.但对于n件物品,每件都可以选择取或者不取,总的可能性有2n, n = 30就大约已经有10亿种可能了!枚举所

动态规划5-最长单增子序列

(LIS Longest Increasing Subsequence)给定一个数列,从中删掉任意若干项剩余的序列叫做它的一个子序列,求它的最长的子序列,满足子序列中的元素是单调递增的. 例如给定序列{1,6,3,5,4},答案是3,因为{1,3,4}和{1,5,4}就是长度最长的两个单增子序列. 处看此题,怎么做? 万能的枚举?枚举全部2^n个子序列,找出最长的,固然可以,就是复杂度太高.我们为什么要枚举呢?因为要知道取了哪些数,其实我们只需要考虑上一个数和取了几个数就可以了吧?因为单增的意思

动规讲解基础讲解八——正整数分组

将一堆正整数分为2组,要求2组的和相差最小.例如:1 2 3 4 5,将1 2 4分为1组,3 5分为1组,两组和相差1,是所有方案中相差最少的. 整数个数n<=100,所有整数的和<=10000 初看题目,第一想到贪心.怎么贪?排序,每次把数放到“最有利”的一边,最有利指的是每次都把数放到使得结果差值尽可能小的那边.这样的方法显然前两个数只能分到不同的组了,这是不对的.比如{1,2,3},这种贪心会把1和2分开,显然得不到最优解. 最优解是{1,2}在一起,3自己在一组. 是不是如果找到一个

动规讲解基础讲解六——编辑距离问题

给定两个字符串S和T,对于T我们允许三种操作: (1) 在任意位置添加任意字符(2) 删除存在的任意字符(3) 修改任意字符 问最少操作多少次可以把字符串T变成S? 例如: S=  “ABCF”   T = “DBFG” 那么我们可以 (1) 把D改为A(2) 删掉G(3) 加入C 所以答案是3. 分析: 这个最少的操作次数,通常被称之为编辑距离.“编辑距离”一次本身具有最短的意思在里面.因为题目有“最短”这样的关键词,首先我们想到的是BFS.是的,当S的距离为m, T的距离为n的时候,我们可以

动规讲解基础讲解四——最大子段和问题

给出一个整数数组a(正负数都有),如何找出一个连续子数组(可以一个都不取,那么结果为0),使得其中的和最大? 例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13.和为20. 看见这个问题你的第一反应是用什么算法? (1) 枚举?对,枚举是万能的!枚举什么?子数组的位置!好枚举一个开头位置i,一个结尾位置j>=i,再求a[i..j]之间所有数的和,找出最大的就可以啦.好的,时间复杂度? (1.1)枚举i,O(n)(1.2)枚举j,O(n)(1.3)求和a[i..j],O(n

动规讲解基础讲解四——矩阵取数

给定一个m行n列的矩阵,矩阵每个元素是一个正整数,你现在在左上角(第一行第一列),你需要走到右下角(第m行,第n列),每次只能朝右或者下走到相邻的位置,不能走出矩阵.走过的数的总和作为你的得分,求最大的得分. 初看此题,你的思路是什么? (1) 贪心? 先走到大的数再说?看这个例子: 无论你以什么方式走到3,总和都是1 + 1 + 3 + 1 + 1 + 1 + 1 = 9我们为了1个3,放弃了那么多个2, 不值啊.如果我们放弃3而走那些2, 得到的和是1 + 1 + 2 + 2 + 2 + 1

【BZOJ3875】【Ahoi2014】骑士游戏 SPFA处理有后效性动规

广告: #include <stdio.h> int main() { puts("转载请注明出处[vmurder]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/44040735"); } 题解: 首先一个点可以分裂成多个新点,这样就有了图上动规的基础. 即f[i]表示i点被消灭的最小代价,它可以由分裂出的点们更新. 但是这个东西有后效性,所以我们用SPFA来处理它. spfa处理后效性动规 我