【CodeForces】CF10D LCIS

题目地址:

https://www.luogu.com.cn/problem/CF10D

题面翻译:
求两个串的最长公共上升子序列。

题目描述:
This problem differs from one which was on the online contest.

The sequence a 1 , a 2 , . . . , a n a_{1},a_{2},...,a_{n} a1,a2,...,an is called increasing, if a i < a i + 1 a_{i}<a_{i+1} ai<ai+1 for i < n i<n i<n .

The sequence s 1 , s 2 , . . . , s k s_{1},s_{2},...,s_{k} s1,s2,...,sk is called the subsequence of the sequence a 1 , a 2 , . . . , a n a_{1},a_{2},...,a_{n} a1,a2,...,an , if there exist such a set of indexes 1 ≤ i 1 < i 2 < . . . < i k ≤ n 1\le i_{1}<i_{2}<...<i_{k}\le n 1i1<i2<...<ikn that a i j = s j a_{i_j}=s_{j} aij=sj . In other words, the sequence s s s can be derived from the sequence a a a by crossing out some elements.

You are given two sequences of integer numbers. You are to find their longest common increasing subsequence, i.e. an increasing sequence of maximum length that is the subsequence of both sequences.

输入格式:
The first line contains an integer n n n ( 1 ≤ n ≤ 500 1\le n\le 500 1n500 ) — the length of the first sequence. The second line contains n n n space-separated integers from the range [ 0 , 1 0 9 ] [0,10^{9}] [0,109] — elements of the first sequence. The third line contains an integer m m m ( 1 ≤ m ≤ 500 1\le m\le 500 1m500 ) — the length of the second sequence. The fourth line contains m m m space-separated integers from the range [ 0 , 1 0 9 ] [0,10^{9}] [0,109] — elements of the second sequence.

输出格式:
In the first line output k k k — the length of the longest common increasing subsequence. In the second line output the subsequence itself. Separate the elements with a space. If there are several solutions, output any.

思路是动态规划。求最长公共子序列长度,可以参考https://blog.csdn.net/qq_46105170/article/details/114242851。设两个序列分别为 a a a b b b,设 f [ i ] [ j ] f[i][j] f[i][j] a [ 1 : i ] a[1:i] a[1:i] b [ 1 : j ] b[1:j] b[1:j]的最长公共上升子序列的长度,并且要求该子序列含 b [ j ] b[j] b[j]。则: f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , 1 + max ⁡ k < j ∧ b [ k ] < a [ i ] f [ i − 1 ] [ k ] } f[i][j]=\max\{f[i-1][j],1+\max_{k<j\land b[k]<a[i]}f[i-1][k]\} f[i][j]=max{f[i1][j],1+k<jb[k]<a[i]maxf[i1][k]}当不含 a [ i ] a[i] a[i]时,最长公共上升子序列长度即为 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j];当含 a [ i ] a[i] a[i]的时候,隐含 a [ i ] = b [ j ] a[i]=b[j] a[i]=b[j],可以以倒数第二个数是哪个来分类,如果不含倒数第二个数,则长度为 1 1 1;如果倒数第二个数是 b [ k ] , k < j b[k],k<j b[k],k<j,隐含 b [ k ] < b [ j ] = a [ i ] b[k]<b[j]=a[i] b[k]<b[j]=a[i],则长度为 f [ i − 1 ] [ k ] + 1 f[i-1][k]+1 f[i1][k]+1。所以不含 a [ i ] a[i] a[i]时,最长长度为 1 + max ⁡ k < j ∧ b [ k ] < a [ i ] { f [ i − 1 ] [ k ] } 1+\max_{k<j\land b[k]<a[i]}\{f[i-1][k]\} 1+maxk<jb[k]<a[i]{f[i1][k]}。我们可以将 i i i 1 ∼ n 1\sim n 1n遍历,将 j j j 1 ∼ m 1\sim m 1m遍历,同时用一个变量 p p p使得 p < j ∧ b [ k ] < a [ i ] p<j\land b[k]<a[i] p<jb[k]<a[i]并且 f [ i − 1 ] [ p ] f[i-1][p] f[i1][p]取到最大值。这样在内层循环里只需要常数的时间更新 f [ i ] [ j ] f[i][j] f[i][j],同时只需要注意更新 p p p即可。最后答案就是 max ⁡ j f [ n ] [ j ] \max_j f[n][j] maxjf[n][j]。为了求具体序列,我们用一个二维数组 g [ i ] [ j ] g[i][j] g[i][j]表示使得 f [ i ] [ j ] f[i][j] f[i][j]取得最大值的时候,倒数第二个数在 b b b中的下标(如果不存在倒数第二个数,则赋值为 0 0 0)。那么要求具体序列,只需要找到使得 f [ n ] [ p ] f[n][p] f[n][p]最大的那个 p p p,接着用 g [ n ] [ p ] g[n][p] g[n][p]替代 p p p不停向前递推即可。代码如下:

#include <iostream>
using namespace std;

const int N = 510;
int n, m;
int a[N], b[N];
int f[N][N], pre[N][N];

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, p = 0; j <= m; j++) {
      f[i][j] = f[i - 1][j];
      pre[i][j] = pre[i - 1][j];
      if (a[i] == b[j] && f[i - 1][p] + 1 > f[i][j]) {
        f[i][j] = f[i - 1][p] + 1;
        pre[i][j] = p;
      }
      if (b[j] < a[i] && f[i - 1][j] > f[i - 1][p])
        p = j;
    }

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

  printf("%d\n", res);

  m = res;
  while (p) {
    a[m--] = b[p];
    p = pre[n][p];
  }

  for (int i = 1; i <= res; i++) printf("%d ", a[i]);
}

时空复杂度 O ( n m ) O(nm) O(nm)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值