LCIS - CodeForces - 10D

LCIS - CodeForces - 10D

1、最长公共上升子序列

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列A和B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列A和B的长度均不超过3000。

输入格式
第一行包含一个整数N,表示数列A,B的长度。

第二行包含N个整数,表示数列A。

第三行包含N个整数,表示数列B。

输出格式
输出一个整数,表示最长公共上升子序列的长度。

数据范围
1≤N≤3000,序列中的数字均不超过231−1

输入样例:
4
2 2 1 3
2 1 2 3
输出样例:
2

分析:

状 态 表 示 : f [ i ] [ j ] : 考 虑 A 的 前 i 个 数 值 中 与 B 的 前 j 个 数 值 , 且 以 b [ j ] 结 尾 的 最 长 公 共 上 升 子 序 列 的 长 度 。   状 态 计 算 : 1 、 若 A [ i ] ≠ B [ j ] , 就 不 考 虑 A [ i ] , 则 f [ i ] [ j ] = f [ i − 1 ] [ j ] 。 2 、 若 A [ i ] = B [ j ] , 就 考 虑 A [ i ] : 倒 数 第 二 个 数 是 B [ k ] 的 最 长 公 共 上 升 子 序 列 , f [ i ] [ j ] = m a x ( f [ i − 1 ] [ k ] + 1 ) , k ∈ [ 1 , j − 1 ] 。 状态表示:f[i][j]:考虑A的前i个数值中与B的前j个数值,且以b[j]结尾的最长公共上升子序列的长度。\\\ \\状态计算:\\1、若A[i]≠B[j],就不考虑A[i],则f[i][j]=f[i-1][j]。\\2、若A[i]=B[j],就考虑A[i]:\\\quad倒数第二个数是B[k]的最长公共上升子序列,f[i][j]=max(f[i-1][k]+1),k∈[1,j-1]。 f[i][j]AiBjb[j] 1A[i]=B[j]A[i]f[i][j]=f[i1][j]2A[i]=B[j]A[i]:B[k]f[i][j]=max(f[i1][k]+1)k[1,j1]

最 后 求 m a x ( f [ n ] [ i ] ) , i ∈ [ 1 , n ] 。 最后求max(f[n][i]),i∈[1,n]。 max(f[n][i])i[1,n]

O(N3)代码:

#include<iostream>
#include<algorithm>

using namespace std;

const int N=3010;

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

int main()
{
    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])
            {
                f[i][j]=max(f[i][j],1);
                for(int k=1;k<j;k++)
                {
                    if(b[k]<b[j])
                        f[i][j]=max(f[i][j],f[i-1][k]+1);
                }
            }
        }
        
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,f[n][i]);
    
    cout<<res<<endl;
    
    return 0;
    
}

总结上述代码思路:

对 每 一 个 A [ i ] , 遍 历 数 组 B , 对 所 有 B [ j ] 满 足 B [ j ] = A [ i ] , 计 算 最 大 上 升 子 序 列 的 长 度 f [ i ] [ j ] = m a x ( f [ i ] [ k ] + 1 ) , 其 中 k ∈ [ 1 , j − 1 ] 。 对每一个A[i],遍历数组B,对所有B[j]满足B[j]=A[i],计算最大上升子序列的长度f[i][j]=max(f[i][k]+1),\\其中k∈[1,j-1]。 A[i]BB[j]B[j]=A[i]f[i][j]=max(f[i][k]+1)k[1,j1]

因 为 是 从 短 到 长 , 并 且 当 A [ i ] = B [ j ] 时 才 计 算 , 因 此 得 到 的 结 果 就 是 最 长 公 共 上 升 子 序 列 的 长 度 。 因为是从短到长,并且当A[i]=B[j]时才计算,因此得到的结果就是最长公共上升子序列的长度。 A[i]=B[j]

但 是 时 间 复 杂 度 很 高 。 但是时间复杂度很高。

优化:

因 为 A [ i ] = B [ j ] , 所 以 第 三 层 的 循 环 与 第 二 层 的 j 是 无 关 的 。 可 以 改 成 A [ i ] > B [ k ] 。 我 们 发 现 , 第 三 层 循 环 是 求 : 满 足 A [ i ] > B [ k ] 的 f [ i − 1 ] [ k ] + 1 且 k ∈ [ 1 , j − 1 ] , 的 最 大 值 。 因为A[i]=B[j],所以第三层的循环与第二层的j是无关的。可以改成A[i]>B[k]。\\我们发现,第三层循环是求:满足A[i]>B[k]的f[i-1][k]+1且k∈[1,j-1],的最大值。 A[i]=B[j]jA[i]>B[k]A[i]>B[k]f[i1][k]+1k[1,j1],

因 此 , 我 们 可 以 用 一 个 变 量 来 存 储 所 有 [ 1 , j − 1 ] 中 的 最 大 值 , 仅 需 在 先 考 虑 A [ i ] = B [ j ] 的 情 况 后 , 更 新 m a x v = m a x ( m a x v , f [ i − 1 ] [ j ] + 1 ) 即 可 , 这 样 下 一 个 循 环 m a x v 就 变 成 了 在 j − 1 时 更 新 的 最 大 值 。 因此,我们可以用一个变量来存储所有[1,j-1]中的最大值,仅需在先考虑A[i]=B[j]的情况后,\\更新maxv=max(maxv,f[i-1][j]+1)即可,这样下一个循环maxv就变成了在j-1时更新的最大值。 [1,j1]A[i]=B[j],maxv=max(maxv,f[i1][j]+1)maxvj1

O(N2)代码:

#include<iostream>
#include<algorithm>

using namespace std;

const int N=3010;

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

int main()
{
    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++)
    {
        int maxv=0;  //0对应倒数第二个数为空 
        for(int j=1;j<=n;j++)
        {
            f[i][j]=f[i-1][j];
            if(a[i]==b[j]) f[i][j]=max(f[i][j],maxv+1);
            if(b[j]<a[i]) maxv=max(maxv,f[i][j]);  //以第[1,j-1]个元素结尾的最大的上升子序列的长度
        }
    }
    
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,f[n][i]);
    
    cout<<res<<endl;
    
    return 0;
}

2、LCIS - CodeForces - 10D

题意:

给 定 一 个 长 度 为 n 的 整 数 序 列 a 和 一 个 长 度 为 m 的 整 数 序 列 b , 求 最 长 公 共 上 升 子 序 列 的 长 度 以 及 路 径 。 给定一个长度为n的整数序列a和一个长度为m的整数序列b,求最长公共上升子序列的长度以及路径。 namb

数据范围:
1 < = n , m < = 500 , a i , b i ∈ [ 0 , 1 0 9 ] 。 T i m e   l i m i t : 1000 m s , M e m o r y   l i m i t : 262144 k B 1<=n,m<=500,a_i,b_i∈[0,10^9]。\\Time\ limit:1000 ms,Memory \ limit:262144 kB 1<=n,m<=500ai,bi[0,109]Time limit1000msMemory limit262144kB

题解:

同 上 题 , 问 题 在 于 求 路 径 。 p a t h [ i ] [ j ] : 记 录 状 态 i , j 时 从 数 组 B 中 哪 个 位 置 来 的 。 用 下 标 k = j 记 录 满 足 f [ i ] [ k ] = m a x ( f [ i − 1 ] [ j ] ) 。 同上题,问题在于求路径。path[i][j]:记录状态i,j时从数组B中哪个位置来的。\\用下标k=j记录满足f[i][k]=max(f[i-1][j])。 path[i][j]i,jBk=jf[i][k]=max(f[i1][j])

考 虑 B 数 组 中 的 路 径 。 考虑B数组中的路径。 B

若 A [ i ] ≠ B [ j ] , 此 时 的 最 长 公 共 上 升 子 序 列 不 变 , f [ i ] [ j ] = f [ i − 1 ] [ j ] , p a t h [ i ] [ j ] = j 。 若A[i]≠B[j],此时的最长公共上升子序列不变,f[i][j]=f[i-1][j],path[i][j]=j。 A[i]=B[j]f[i][j]=f[i1][j]path[i][j]=j

若 A [ i ] > B [ j ] , 并 且 f [ i ] [ j ] > f [ i − 1 ] [ k ] , 则 更 新 k = j 。 若A[i]>B[j],并且f[i][j]>f[i-1][k],则更新k=j。 A[i]>B[j]f[i][j]>f[i1][k]k=j

若 A [ i ] = B [ j ] , p a t h [ i ] [ j ] = k , 即 状 态 i , j 是 由 B [ k ] 转 移 而 来 的 , 同 时 f [ i ] [ j ] = f [ i − 1 ] [ k ] + 1 。 若A[i]=B[j],path[i][j]=k,即状态i,j是由B[k]转移而来的,同时f[i][j]=f[i-1][k]+1。 A[i]=B[j]path[i][j]=ki,jB[k]f[i][j]=f[i1][k]+1


代码:

#include<iostream>
#include<algorithm>
#include<cstdio>

using namespace std;

const int N=510;

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

void output(int i,int j)
{
    if(i==0) return ;
    output(i-1,path[i][j]);
    if(path[i][j]!=j) printf("%d ",b[j]);
    return ;
}

int main()
{
    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++)
    {
        for(int j=1,k=0;j<=m;j++)
        {
            f[i][j]=f[i-1][j];
            path[i][j]=j;
            if(a[i]==b[j])
            {
                f[i][j]=f[i-1][k]+1;
                path[i][j]=k;
            }

            if(a[i]>b[j] && f[i][j]>f[i-1][k]) k=j;

        }
    }

    int res=0,pos=0;;
    for(int i=1;i<=m;i++) if(res<f[n][i]) {res=f[n][i];pos=i;}

    cout<<res<<endl;
    output(n,pos);

    return 0;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值