CF10D LCIS
题目描述
This problem differs from one which was on the online contest.
The sequence a_{1},a_{2},...,a_{n}a1,a2,...,a**n is called increasing, if a_{i}<a_{i+1}a**i<a**i+1 for i<ni<n .
The sequence s_{1},s_{2},...,s_{k}s1,s2,...,s**k is called the subsequence of the sequence a_{1},a_{2},...,a_{n}a1,a2,...,a**n , if there exist such a set of indexes 1<=i_{1}<i_{2}<...<i_{k}<=n1<=i1<i2<...<i**k<=n that a_{ij}=s_{j}aij=s**j . In other words, the sequence ss can be derived from the sequence aa 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 nn ( 1<=n<=5001<=n<=500 ) — the length of the first sequence. The second line contains nn space-separated integers from the range [0,10^{9}][0,109] — elements of the first sequence. The third line contains an integer mm ( 1<=m<=5001<=m<=500 ) — the length of the second sequence. The fourth line contains mm space-separated integers from the range [0,10^{9}][0,109] — elements of the second sequence.
输出格式
In the first line output kk — 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.
题意翻译
求两个串的最长公共上升子序列。
题解:
给出两个数列,求这两个数列的LCIS(最长公共上升子序列)的长度,并输出数列。
问题拆分,先考虑长度怎么求。
首先你要会LIS和LCS这两个问题的求法(经典DP)。LCS的状态是:设置(f[i][j])表示A数列前i个元素,B数列前j个元素的LCS长度。LIS的状态是:设置(f[i])表示以A[i]结尾的LIS长度。
也就是说,要求LCS,你得把两个数列都加到状态里去,要求LIS,你得知道结尾元素是啥,要不然没法更新。
所以综合一下,就变成了LCIS的状态:(f[i][j])表示A数列前i个元素,B数列前j个元素的LCIS长度,同时,B数列的结尾元素为B[j]。
所以人类的本质是累加
转移的过程是也是LIS、LCS转移过程的大致加和。
首先要满足LCS的性质,也就是说要分A[i]=B[i],A[i]( eq)B[i]两种情况。并且,当A[i]=B[i]的时候,还需要满足LIS的性质,也就是需要再加一层循环来判断。
那么就有了以下转移:
(A[i] eq B[i]):(f[i][j]=f[i-1][j])
(A[i]=B[i]):(max_{kin[0,j)}{f[i-1][k]+1})。
因为涉及到B[0],所以直接设成无穷小(此处-1即可)。
这样的话,答案就是(max_{iin[1,m]}{f[n][i]})
更加证实了人类的本质是累加
下一个问题来了,如何记录这个数列。
通过之前的讲解,容易知道的是我们可以在最后统计答案的时候记录答案序列的结尾元素是什么。
如果我们可以通过一种记录,使得知道了结尾元素就能知道整个序列,那该多好。
可以实现:新开一个(path[i][j])数组表示A数列前i个元素,B数列前j个元素且以B[j]为结尾的LCIS的倒数第二位编号,即可实现迭代查找。
全篇代码如下:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=501;
int n,m,pos,tot,num;
int a[maxn],b[maxn],path[maxn][maxn],ans[maxn];
int f[maxn][maxn];//f[i][j]表示a列前i个,b列前j个数且以b[j]结尾的LCIS长度
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]);
a[0]=b[0]=-1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(a[i]==b[j])
{
for(int k=0;k<j;k++)
if(b[k]<a[i])
if(f[i-1][k]+1>f[i][j])
{
path[i][j]=k;
f[i][j]=f[i-1][k]+1;
}
}
else
{
f[i][j]=f[i-1][j];
path[i][j]=path[i-1][j];
}
}
for(int i=1;i<=m;i++)
if(tot<f[n][i])
{
tot=f[n][i];
pos=i;
}
printf("%d
",tot);
while(pos)
{
ans[++num]=b[pos];
pos=path[n][pos];
}
for(int i=num;i>=1;i--)
printf("%d ",ans[i]);
return 0;
}