AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 272. 最长公共上升子序列【LCIS + 优化】    原题链接    中等

作者: 作者的头像   一只野生彩色铅笔 ,  2021-06-06 19:50:03 ,  所有人可见 ,  阅读 7270


124


30

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

给定两个长度为 $n$ 个数组 $a[n], b[n]$

求两个数组的 最长公共上升子序列 长度

解析

这是两个经典DP模型的结合版:

$\text{LIS}$ (最长上升子序列,Longest Increasing Subsequence)

$\text{LCS}$ (最长公共子序列,Longest Common Subsequence)

$\text{LCIS}$ (最长公共上升子序列,Longest Common Increasing Subsequence)

LCIS 也是一个相当经典的DP模型,他的 状态分析 是 LIS 与 LCS 的结合,且听我慢慢道来


闫氏DP分析法:(结合了LCS与LIS的状态表示的方法,可以很直接的发现二者的影子)

状态表示 f[i][j]—集合:考虑 a 中前 i 个数字,b 中前 j 个数字 ,且当前以 b[j] 结尾的子序列的方案

状态表示 f[i][j]—属性:该方案的子序列长度最大 $\max$

状态转移 :

  1. 从考虑 a数组 中前 i-1 个数字, b数组 中前 j 个数字 ,且当前以 b[j] 结尾的子序列的方案转移过来:
    $f_{i,j}=\max(f_{i,j}, f_{i-1,j})$

  2. 从考虑 a数组 中前 i 个数字, b数组 中前 k 个数字 ,且当前以 b[k] 结尾的子序列的方案转移过来:
    $f_{i,j}=\max(f_{i,j}, f_{i-1,k} + 1) \quad k\in[0,j-1],a_i = b_j,b_j>b_k$

初始状态:f[0][0]

目标状态:f[n][i]

集合划分

IMG_6FD39448531E-1.jpeg

IMG_BF36A452CBB7-1.jpeg

Code(朴素版)

时间复杂度:$O(n^3)$

对于本题的数据规模,毫无疑问会超时

这里贴出代码是方便大家理解这个DP模型,因为接下来的优化,就和DP无关,是一个代码的等价变形优化

#include <iostream>

using namespace std;

const int N = 3010;

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

int main()
{
    //input
    cin >> n;
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    for (int i = 1; i <= n; ++ i) cin >> b[i];

    //dp
    for (int i = 1; i <= n; ++ i)
    {
        for (int j = 1; j <= n; ++ j)
        {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j])
            {
                for (int k = 0; k < j; ++ k)
                {
                    if (b[j] > b[k])
                    {
                        f[i][j] = max(f[i][j], f[i - 1][k] + 1);
                    }
                }
            }
        }
    }

    //find result
    int res = 0;
    for (int i = 0; i <= n; ++ i) res = max(res, f[n][i]);
    cout << res << endl;

    return 0;
}

Code(优化版)

我们可以观察到,对于第二种状态转移:$f_{i,j}=\max(f_{i,j}, f_{i-1,k} + 1) \quad k\in[0,j-1],a_i = b_j,b_j>b_k$

每次用到的 状态 都是第 i - 1 个阶段的

因此我们可以用一个变量,存储上一个阶段的能够接在 a[i] 前面的最大的状态值

时间复杂度:$O(n^2)$

#include <iostream>

using namespace std;

const int N = 3010;

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

int main()
{
    //input
    cin >> n;
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    for (int i = 1; i <= n; ++ i) cin >> b[i];

    //dp
    for (int i = 1; i <= n; ++ i)
    {
        int maxv = 1;
        for (int j = 1; j <= n; ++ j)
        {
            f[i][j] = f[i - 1][j];
            if (b[j] == a[i]) f[i][j] = max(f[i][j], maxv);
            if (b[j] < a[i]) maxv = max(maxv, f[i - 1][j] + 1);
        }
    }

    //find result
    int res = 0;
    for (int i = 0; i <= n; ++ i) res = max(res, f[n][i]);
    cout << res << endl;

    return 0;
}

31 评论


用户头像
慕斯喵   2022-03-13 11:11      8    踩      回复

k从0开始循环的写法在遇到数列中有非正整数的时候会出错,因为末尾两数相等的情况和0比较无法更新成1,建议在循环前直接将 f[i][j]置为max(f[i][j],1),然后从k1开始循环

用户头像
依赖   2023-05-02 00:23      1    踩      回复

为什么要从0开始呢,或者为什么要跟1比较一下大小捏?

用户头像
糖豆   2023-05-09 13:19    回复了 依赖 的评论      1    踩      回复

从0开始是让1也走这个逻辑,其实,1可以初始值,可以直接给初值的,不是一定要从0开始。

#include <bits/stdc++.h>
using namespace std;

const int N = 3010;
int a[N], b[N];
int f[N][N];
int res;

// O(n^3)
int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j]) {
                int maxv = 1;
                for (int k = 1; k < j; k++)
                    if (a[i] > b[k])
                        maxv = max(maxv, f[i - 1][k] + 1);
                f[i][j] = max(f[i][j], maxv);
            }
        }
    int res = 0;
    for (int i = 1; i <= n; i++) res = max(res, f[n][i]);
    printf("%d\n", res);
    return 0;
}

这里k就是从1开始的,maxv=1就是初始化,理解为现在a[i]==b[j],是不是两个序列最少有1个LICS长度了,最少是1,其它的发现1个就增加1个长度。

用户头像
依赖   2023-05-09 16:03    回复了 糖豆 的评论         踩      回复

理解咯,谢谢你


用户头像
依赖   2023-06-07 23:32      1    踩      回复

优化的时候请问一下为什么要加上if(b[j] < a[i])呢


用户头像
Moon_light   2022-07-05 17:37      1    踩      回复

为什么 f[i][j]不能由f[i][j-1]转移过来呀

用户头像
是金子总会发光   2022-08-19 14:03         踩      回复

f[i][j-1]是以b[j-1]结尾的LCIS,f[i][j]是以b[j]结尾的LCIS,b[j]的值会对f[i][j]有影响(如果b[j-1]>b[j]则对于以b[j-1]的结尾的最优解跟以b[j]的结尾的最优解不一样,假设以b[j-1]的结尾的最优解序列为b[k1],b[k2],b[j],b[k2]也大于b[j](((我也不知道我在说什么

用户头像
H.Z自白   2022-12-14 22:30      1    踩      回复

一开始我感觉f[i][j]当a[i]!=b[j]时这题应该是f[i][j]=max(f[i-1][j],f[i][j-1]) 但是结果就错了,仔细想一想发现最长上升子序列是针对一个数组的,而最长公共子序列在针对两个数组的,因此这题必须得将重心放在一个序列上,y总取的是以b[j]结尾的意思就是,以第二个序列作为重心,因此如果不相等的话,那么f[i][j]=f[i-1][j],如果相等的话就在b数组中找最长公共上升子序列最大值,

用户头像
Boder_q   2024-01-25 18:59    回复了 H.Z自白 的评论         踩      回复

good


用户头像
锦_48   2024-12-20 20:23         踩      回复

##牛逼!!


用户头像
decoder50   2023-11-24 12:23         踩      回复

orz终于得救了


用户头像
Happy_hacking   2023-11-01 10:29         踩      回复

这个才是真正的朴素版


用户头像
hya   2023-03-24 17:14         踩      回复

朴素版中f[i][j] = max(f[i][j], f[i - 1][k] + 1);这一步y总写的是f[i][j] = max(f[i][j], f[i][k] + 1),这为什么能过....依赖的层数都不一样,一个是i-1,一个是i,而且都是从1开始


用户头像
rsy_   2023-01-17 09:45         踩      回复

佬


用户头像
武破立法   2023-01-03 19:02         踩      回复

请问大佬,先把最长公共子序列的 序列求出来, 再去找里面的最长递增子序列?

用户头像
武破立法   2023-01-03 19:02         踩      回复

请问大佬,可不可以先把最长公共子序列的 序列求出来, 再去找里面的最长递增子序列?

用户头像
1357649762   2023-01-04 17:05    回复了 武破立法 的评论      1    踩      回复

不可以, 比如1 2 3 9 8 7 6 5 4序列和 9 8 7 6 5 4 1 2 3 序列,筛选出的最长公共子序列应该是9 8 7 6 5 4,但是我们需要的最长公共上升子序列应该是 1 2 3

用户头像
武破立法   2023-01-05 09:37    回复了 1357649762 的评论         踩      回复

好的好的谢谢大佬!


用户头像
nope_ck   2022-10-25 18:59         踩      回复

不懂为什么循环中k从0开始阿

用户头像
今天也是刷题的一天   2023-03-14 09:02         踩      回复

我也。。佬知道为什么吗

用户头像
今天也是刷题的一天   2023-03-14 09:23      4    踩      回复

好吧刚试了一下,是初始化的问题,从0开始等效于在第三层循环之前加一句f[i][j] = max(f[i][j], 1)

用户头像
半夜不学算法   2024-02-05 16:14         踩      回复

这里的从0开始实际上就是给f[i][j]初始化为1,但是并不建议这样些,最好还是在循环开始就初始化一下初始值


用户头像
Moyou   2022-07-20 11:07         踩      回复

懂了,谢谢大佬%%%


用户头像
飞龙在天QAQ   2022-07-04 20:11         踩      回复

orz%%%


用户头像
最傻的猪   2022-05-02 15:42         踩      回复
    for (int i = 1; i <= n; ++ i)
    {
        int maxv = 1;
        for (int j = 1; j <= n; ++ j)
        {
            f[i][j] = f[i - 1][j];
            if (b[j] == a[i]) f[i][j] = max(f[i][j], maxv);
            if (b[j] < a[i]) maxv = max(maxv, f[i - 1][j] + 1);
        }
    }

第二重循环里的两个if条件不能交换位置吧,但原题好像也可AC。

用户头像
旗木卡卡鸡   2022-05-04 19:38      1    踩      回复

可以交换位置的兄弟,因为这两个if条件是相互对立的呀,满足其中一个的话另一个必不可以满足

用户头像
最傻的猪   2022-05-05 07:58    回复了 旗木卡卡鸡 的评论         踩      回复

对,谢谢大佬


用户头像
tsrigo   2022-02-13 21:46         踩      回复

可以请问一下QAQ,在第二种情况中“从以 b[k] 结尾的子序列的方案转移过来”,为什么不用在代码中判断一下,a[N]数组是否有对应的元素呢

用户头像
一只野生彩色铅笔   2022-02-13 21:51         踩      回复

k 枚举的是 b[] 中的下标,所有一定存在
所以只需判断大小关系即可,即 b[j] > b[k]

用户头像
tsrigo   2022-02-13 22:16    回复了 一只野生彩色铅笔 的评论         踩      回复

谢谢解答QAQ


用户头像
阿吉迷得牛炖咖喱略   2021-09-13 17:33         踩      回复

orz___


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息