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]:考虑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]=max(f[i−1][k]+1),k∈[1,j−1]。
最 后 求 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],遍历数组B,对所有B[j]满足B[j]=A[i],计算最大上升子序列的长度f[i][j]=max(f[i][k]+1),其中k∈[1,j−1]。
因 为 是 从 短 到 长 , 并 且 当 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],所以第三层的循环与第二层的j是无关的。可以改成A[i]>B[k]。我们发现,第三层循环是求:满足A[i]>B[k]的f[i−1][k]+1且k∈[1,j−1],的最大值。
因 此 , 我 们 可 以 用 一 个 变 量 来 存 储 所 有 [ 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,j−1]中的最大值,仅需在先考虑A[i]=B[j]的情况后,更新maxv=max(maxv,f[i−1][j]+1)即可,这样下一个循环maxv就变成了在j−1时更新的最大值。
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,求最长公共上升子序列的长度以及路径。 给定一个长度为n的整数序列a和一个长度为m的整数序列b,求最长公共上升子序列的长度以及路径。
数据范围:
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<=500,ai,bi∈[0,109]。Time limit:1000ms,Memory limit:262144kB
题解:
同 上 题 , 问 题 在 于 求 路 径 。 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,j时从数组B中哪个位置来的。用下标k=j记录满足f[i][k]=max(f[i−1][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[i−1][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[i−1][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]=k,即状态i,j是由B[k]转移而来的,同时f[i][j]=f[i−1][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;
}