线性dp+最长上升子序列

82 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情

题目

www.acwing.com/problem/con… image.png

分析

image.png

  1. 子序列:从前往后挑,可以有间隔的挑选
  2. 状态表示f[i]:
    1. 集合:所有以第i个数结尾的上升子序列
    2. 属性:上升子序列长度的最大值Max
  3. 状态计算:
    1. 确定:第i个数是确定的
    2. 我们以第i - 1个数来分类
      1. 第一类:没有第i - 1 个数,也就是序列长度是1
      2. 第二类:倒数第二个数是原序列第一个数
      3. 第三类:倒数第二个数是原序列第二个数
      4. 第四类:倒数第二个数是原序列第三个数
      5. ……
      6. 最后一类:倒数第二个数是原序列第i - 1个数
  4. 时间复杂度:O(n^2)

代码

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
int a[N], f[N];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    for (int i = 1; i <= n; i++)
    {
        f[i] = 1; // 最长上升子序列长度最少是1,只有a[i]这一个数
    	for (int j = 1; j < i; j++)
            if (a[j] < a[i])
                f[i] = max(f[i], f[j] + 1);
    }
    
    int res = 0;
    for (int i = 1; i <= n; i++) res = max(res, f[i]);
    
    printf("%d\n", res);
    
    return 0;
}

怎样将最长序列保存下来

  1. 用g[N]数组来保存一下数据
  2. g[i] = 0:表示只有一个数
  3. 动态规划将最长子序列存下来,其实就是将转移存下来即可
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
int a[N], f[N], g[N];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    for (int i = 1; i <= n; i++)
    {
        f[i] = 1; // 最长上升子序列长度最少是1,只有a[i]这一个数
    	g[i] = 0;
        for (int j = 1; j < i; j++)
            if (a[j] < a[i])
                if (f[i]  < f[j] + 1)
                {
                    f[i] = f[j] + 1;
                    g[i] = j;
                }
        
    }
    
    int k = 1;
    for (int i = 1; i <= n; i++) 
        if (f[k] < f[i])
        {
            k = i;	
        }
    
    printf("%d\n", f[k]);
    
    // 倒着来输出
    for (int i = 0, len = f[k]; i < len; i++)
    {
        printf("%d ", a[k]);
        k = g[k];
    }
    
    printf("%d\n", res);
    
    return 0;
}