LCIS
◇题意◇
给定 n,m n , m ,并给定长度分别为 n,m n , m 的两个数组 a[],b[] a [ ] , b [ ] ,输出 a,b a , b 数组的最长公共子序列的长度和它本身。
n,m(1 ≤n,m ≤ 500) n , m ( 1 ≤ n , m ≤ 500 )
听说 O(n4) O ( n 4 ) 都能过
◇经典 Dp D p ◇
单纯的我
摆出上面两道题跟这道题还是有一定关系的,但是为了不对大家产生误导,我先谈谈我做这道题的经过。
看到这道题的名字,先瞬间分解成了:
于是我用取出“最长公共子序列”取出公共子序列,再用“最长上升子序列”的方法求出答案。
乍一眼看很有道理啊,但经过几次“ Wonderful W o n d e r f u l Answer A n s w e r ”后
发现在取出时,我们并不能保证取出的“最长公共子序列”包含“最长公共上升子序列”。
Example
a: a : 7 7 5 5 4 4 7 7
7 7 5 5 6 6 2 2
按照递归的取“最长公共子序列”,取出:
1 1 6 6
此序列的“最长上升子序列”为:
1 1 6 6 (len=3)
但原序列的“最长公共上升子序列”为:
5 5 7 7 (len=4)
所以这就不对了。
基本思路
说句大实话,我真的不知道怎么写,所以思路从
O(n3)
O
(
n
3
)
开始。
我们(根据以往经验)知道这道题中需要两个变量
i,j
i
,
j
(
a[],b[]
a
[
]
,
b
[
]
中的下标)
好吧,直接说(
dp
d
p
怎么想出来的貌似不太好说清楚)
状态定义:
f[i][j]: f [ i ] [ j ] : 在 a[1...i] a [ 1... i ] 中以 b[j] b [ j ] 结尾的“最长公共子序列”的长度
状态转移:
1≤i≤n,1≤j≤m,1≤k<j 1 ≤ i ≤ n , 1 ≤ j ≤ m , 1 ≤ k < j
解释一下
a[i]!=b[j]
a
[
i
]
!
=
b
[
j
]
:
i
i
之前的以结尾的序列自然没有改变,仍然是长度仍然是
f[i−1][j]
f
[
i
−
1
]
[
j
]
;
a[i]=b[j]
a
[
i
]
=
b
[
j
]
:有一个新的公共元素
b[j]
b
[
j
]
,把它放到序列尾部,固枚举
k
k
求出之前最长的序列并加上去。
差不多了。
◇代码和更进一步◇
O(n3) O ( n 3 )
思路如上。
代码如下。
(我真是充满诗性)
(Dp后的递归输出如有疑问,请参见
Dp
D
p
后的递归输出)
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=int(5e2+5);
int l1,l2;
int f[MAXN][MAXN];
int a[MAXN],b[MAXN];
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;
}
}
int main()
{
int ans=0,a1,b1;
scanf("%d",&l1);
for(int i=1;i<=l1;i++)
scanf("%d",&a[i]);
scanf("%d",&l2);
for(int i=1;i<=l2;i++)
scanf("%d",&b[i]);
for(int i=1;i<=l1;i++)
for(int j=1;j<=l2;j++) {
if(a[i]!=b[j])
f[i][j]=f[i-1][j];
else {
int Max=0;
for(int k=1;k<j;k++)
if(b[k]<b[j])
Max=max(Max,f[i-1][k]);
f[i][j]=Max+1;
if(ans<f[i][j])
ans=f[i][j],a1=i,b1=j;
}
}
printf("%d\n",ans);
if(ans)
Print(a1,b1);
}
O(n2) O ( n 2 )
思路
我们看一下上面那篇代码中,让我们把复杂度升到让人惊恐(
5003
500
3
不太慌)的
O(n3)
O
(
n
3
)
是什么。
对对对,除了必要的
i|1≤i≤n,j|1≤j≤m
i
|
1
≤
i
≤
n
,
j
|
1
≤
j
≤
m
的枚举外,还有转移时的枚举
k|1≤k<j
k
|
1
≤
k
<
j
。
所以,考虑从
k
k
入手优化。
看一下都做了什么:枚举
max(f[i−1][1..(j−1)])
m
a
x
(
f
[
i
−
1
]
[
1..
(
j
−
1
)
]
)
。
但仔细一想,这是没有必要的,注意下面这两段循环。
可以看到,在 a[i]=b[j] a [ i ] = b [ j ] 进入 k k 的循环之前,我们已经扫描过一次所有的了(在当前 j j 之前的),即所有的 f[i−1][k] f [ i − 1 ] [ k ] 都在本层 i i 的循环中可以被扫描。
故我们可以在的循环中找到 max(f[i−1][k]) m a x ( f [ i − 1 ] [ k ] ) 。
这里我们要满足一个条件:
怎么在 i i 循环中满足当前未知的呢?
很容易的,观察一下进入 k k 循环的条件:
所以
其他操作都一样了。
代码如下
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=int(5e2+5);
int l1,l2;
int f[MAXN][MAXN];
int a[MAXN],b[MAXN];
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;
}
}
int main()
{
int ans=0,a1,b1;
scanf("%d",&l1);
for(int i=1;i<=l1;i++)
scanf("%d",&a[i]);
scanf("%d",&l2);
for(int i=1;i<=l2;i++)
scanf("%d",&b[i]);
for(int i=1;i<=l1;i++) {
int Max=0;
for(int j=1;j<=l2;j++) {
if(a[i]!=b[j])
f[i][j]=f[i-1][j];
if(a[i]>b[j]&&Max<f[i-1][j])
Max=f[i-1][j];
if(a[i]==b[j]) {
f[i][j]=Max+1;
if(f[i][j]>ans)
ans=f[i][j],a1=i,b1=j;
}
}
}
printf("%d\n",ans);
if(ans)
Print(a1,b1);
}
如果你没有戳开上文的
Dp
D
p
后的递归输出
再给一次机会!!!戳它
→
→
Dp
D
p
后的递归输出
好吧,如果你再次选择了拒绝。
下面的代码或许会带来些帮助。
接下来要讲的是
其实,这样的做法,跟上面的做法本质上相同,故思路就不在重讲。
具体实现是开一个 vector<int>G[] v e c t o r < i n t > G [ ]
用 G[] G [ ] 存储路径, G.size() G . s i z e ( ) 表示长度,转移直接对 G[] G [ ] 进行操作就行了。
但这个在空间和时间上,都没上一种优秀。
但胜在简单,直观。
代码
对照上一篇看
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=int(5e2+5);
int l1,l2;
int a[MAXN],b[MAXN];
int tap;
vector<int>ans[MAXN],ans1;
int main()
{
scanf("%d",&l1);
for(int i=1;i<=l1;i++)
scanf("%d",&a[i]);
scanf("%d",&l2);
for(int i=1;i<=l2;i++)
scanf("%d",&b[i]);
for(int j=1;j<=l2;j++) {
ans1.clear();
for(int i=1;i<=l1;i++) {
if(b[j]>a[i]&&ans[i].size()>ans1.size())
ans1=ans[i];
if(b[j]==a[i]) {
ans[i]=ans1;
ans[i].push_back(a[i]);
}
}
}
for(int i=1;i<=l1;i++)
if(tap==0||ans[i].size()>ans[tap].size())
tap=i;
printf("%d\n",ans[tap].size());
for(int i=0;i<ans[tap].size();i++)
printf("%d ",ans[tap][i]);
}
再说两句
考试的时候,有位同学
O(n4)
O
(
n
4
)
比我的
O(n2)
O
(
n
2
)
测出来还要快(或许是我太菜),这告诉我们
能写暴力就暴力
Thanks T h a n k s for f o r reading r e a d i n g !