【CodeForces】【DP】10D LCIS

本文详细介绍了解决 CodeForces10D LCIS 题目的四种算法,从 O(N^4) 的暴力解法逐步优化至 O(N^2) 的高效算法,并提供最终代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CodeForces 10D LCIS

题目

◇题目传送门◆

题目大意

给定两个长度分别为 N,M N , M 的序列 A,B A , B ,求两个序列的最长公共上升子序列并输出方案。

思路

首先拿到这道题,我就决定将这道题分解为两个问题:最长上升子序列,最长公共子序列。

但是,交到CF上后,却~~成功地~~WA了。(QAQ。。。)

我开始了我的探索之旅。。。

Step1.时间复杂度为 O(N4) O ( N 4 ) 的暴力算法

可以仿造最长上升子序列的状态定义:定义 f[i][j] f [ i ] [ j ] 为以 A[i],B[j] A [ i ] , B [ j ] 为结尾的最长公共上升子序列。

不难列出状态转移方程

f[i][j]={max(f[k][l])+10A[i]=B[j],A[i]>A[k]A[i]B[j] f [ i ] [ j ] = { max ( f [ k ] [ l ] ) + 1 A [ i ] = B [ j ] , A [ i ] > A [ k ] 0 A [ i ] ≠ B [ j ]

其中 1iN,1jM,i<ki,j<lj 1 ≤ i ≤ N , 1 ≤ j ≤ M , i < k ≤ i , j < l ≤ j

代码非常水,请读者自行实现。

Step2.时间复杂度为 O(N3) O ( N 3 ) 的算法

我们都知道,LCIS是由LCS和LIS演变而来,所以,我们分析一下LCS和LIS的算法:

在LIS中, f[i] f [ i ] 表示以 A[i] A [ i ] 结尾的最长上升子序列的长度,LCS中 f[i][j] f [ i ] [ j ] 表示以 A[1i],B[1j] A [ 1 … i ] , B [ 1 … j ] 的最长公共子序列的长度。

为什么LIS要这么定义???

因为我们在计算 f[i] f [ i ] 时,需要考虑上一个值,这样才能确定是否将 A[i] A [ i ] 加入,而在LCS中,当我们计算 f[i][j] f [ i ] [ j ] 时,是不需要考虑上一个值的,只需考虑当前值即可。

而在LCIS中,我们也需要知道上一个值的信息, f[i][j] f [ i ] [ j ] ,也就是上一个值的信息为 A[i],B[j] A [ i ] , B [ j ] ,而当 A[i]=B[j] A [ i ] = B [ j ] 时,信息是重复的。所以,我们要消除这种重复。

综上所述,我们改变一下状态定义:

定义 f[i][j] f [ i ] [ j ] 为在 A[1i],B[1j] A [ 1 … i ] , B [ 1 … j ] 上的LCIS,并在 B[j] B [ j ] 处结尾。

则得出状态转移方程:

f[i][j]={f[i1][j]max(f[i1][k])A[i]B[j]1k<j,B[j]>B[k] f [ i ] [ j ] = { f [ i − 1 ] [ j ] A [ i ] ≠ B [ j ] max ( f [ i − 1 ] [ k ] ) 1 ≤ k < j , B [ j ] > B [ k ]

其中: 1iN,1jM 1 ≤ i ≤ N , 1 ≤ j ≤ M

自然得出代码:(由于CF的评测机升级过一次,本题又是升级前的题,故 O(N3) O ( N 3 ) 的算法能够过)

#include<cstdio>
#include<algorithm>
using namespace std;

const int Maxn=500;

int N,M;
int A[Maxn+5],B[Maxn+5];
int f[Maxn+5][Maxn+5];

int main() {
    #ifdef LOACL
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
        scanf("%d",&A[i]);
    scanf("%d",&M);
    for(int i=1;i<=M;i++)
        scanf("%d",&B[i]);
    int ans=0;
    for(int i=1;i<=N;i++)
        for(int j=1;j<=M;j++) {
            if(A[i]!=B[j])f[i][j]=f[i-1][j];
            else {
                int tmp=0;
                for(int k=1;k<j;k++)
                    if(f[i-1][k]>tmp&&B[j]>B[k])
                        tmp=f[i-1][k];
                f[i][j]=tmp+1;
                if(ans<f[i][j])
                    ans=f[i][j];
            }
        }
    printf("%d\n",ans);
    return 0;
}

Step3.时间复杂度为 O(N2) O ( N 2 ) 的算法

还可以优化???

我们若要对它优化,也只有优化当 A[i]=B[j] A [ i ] = B [ j ] 的状态转移了。因为外层的两个循环,是无论如何也搞不掉的。

看看这段代码:

int tmp=0;
for(int k=1;k<j;k++)
    if(f[i-1][k]>tmp&&B[j]>B[k])
        tmp=f[i-1][k];
f[i][j]=tmp+1;

A[i]=B[j] A [ i ] = B [ j ] 时,就等价于:

int tmp=0;
for(int k=1;k<j;k++)
    if(f[i-1][k]>tmp&&A[i]>B[k])
        tmp=f[i-1][k];
f[i][j]=tmp+1;

这是在求在 f[i1][k](1k<j) f [ i − 1 ] [ k ] ( 1 ≤ k < j ) 中,满足 A[i]>B[k] A [ i ] > B [ k ] 的最大值。

我们不难发现: i i 的值是不变的,而j的值是保持递增的,所以,我们可以考虑保存一个变量 t t ,记录一下在f[i1][k](1k<j)中,满足 A[i]>B[k] A [ i ] > B [ k ] 的最大值。当 j j 增加时,就可以只用O(1)的时间更新 t t f[i][j]。时间复杂度就成功的降到了 O(N2) O ( N 2 )

Step4.空间复杂度为 O(N) O ( N ) 的算法

我们甚至可以采用滚动数组来进行DP。

但我们在输出路径时就不能使用递归输出,而应多开一个数组来记录路径。

正解代码

因为要输出路径,所以我们需要开一个pre数组来记录状态转移的路径。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Maxn=500;
int N,M;
int A[Maxn+5],B[Maxn+5];
int f[Maxn+5],pre[Maxn+5];
int ans[Maxn+5];
int main() {
    #ifdef LOACL
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
        scanf("%d",&A[i]);
    scanf("%d",&M);
    for(int i=1;i<=M;i++)
        scanf("%d",&B[i]);

    for(int i=1;i<=N;i++) {
        int pos=0;
        for(int j=1;j<=M;j++) {
            if(A[i]==B[j])f[j]=f[pos]+1,pre[j]=pos;
            if(A[i]>B[j]&&f[j]>f[pos])
                pos=j;
        }
    }
    int maxx=-1,pos;
    for(int i=1;i<=M;i++)
        if(f[i]>maxx) {
            maxx=f[i];
            pos=i;
        }

    for(int i=maxx;i>=0;i--) {
        ans[i]=B[pos];
        pos=pre[pos];
    }
    printf("%d\n",maxx);
    for(int i=1;i<=maxx;i++)
        printf("%d ",ans[i]);
    return 0;
}

如果你采用的二维数组,也可以采用递归输出:

void Print(int x,int y) {
    if(f[x][y]==1) {
        printf("%d ",B[y]);
        return;
    }
    if(x==0||y==0)
        return;
    if(f[x][y]==f[x-1][y]) {
        Print(x-1,y);
        return;
    }
    for(int i=y-1;i>=1;i--)
        if(B[i]<B[y]&&f[x][y]==f[x-1][i]+1) {
            Print(x,i);
            printf("%d ",B[y]);
            return;
        }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值