LIS问题:
设(f[i])为以(a[i])结尾的最长上升子序列长度,有:
可以用树状数组优化至(O(nlogn))
基于排列的LCS问题((a,b)均为排列,即一个元素不会出现多次):
设(pos_i)为(a_i)在(b)中出现的位置,即(a_i=b_pos_i)。
(a)的一个子序列(a_p_1,a_p_2,...,a_p_m)是(a,b)的公共子序列等价于(pos_p_1<pos_p_2<...<pos_p_m)
求一个LIS即可。
一般LCS问题:
- 经典解法:
设(f[i][j])表示只考虑(a)中前(i)个,(b)中前(j)个的最长公共子序列长度,有:
十分简单,但是还有一种稍微复杂但是拓展性更高的做法:
设$f[i][j]$表示只考虑$a$中前$i$个,$b$中前$j$个并且$b_j$已经和$a_1,...,a_i$中的某一个匹配的最长公共子序列长度,有:
为什么说这样拓展性更好?来看这样一道题
题目要求最长上升公共子序列,不能直接用LCS的经典解法了,但是我们仔细思考一下,发现如果我们用上面的转移方程,我们只需要在从(f[i-1][k])转移到(f[i][j])时,只需要保证(b[k]<b[j])即可,所以我们得到新的转移方程:
又因为当(a[i]==b[j])时,(b[k]<b[j])等价于(b[k]<a[i]),在转移枚举(j)时对所有(b[k]<a[i])的(f[i-1][k])记录一个前缀(max)即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 5007
int f[N],a[N],b[N];
int main()
{
int i,j,n;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<=n;i++)
scanf("%d",&b[i]);
int maxx=0,ans=0;
for(i=1;i<=n;i++)
{
maxx=0;
for(j=1;j<=n;j++)
{
if(b[j]==a[i])f[j]=max(f[j],maxx+1);
else if(b[j]<a[i])maxx=max(maxx,f[j]);
ans=max(ans,f[j]);
}
}
printf("%d
",ans);
return 0;
}
当然还有对于一般LCS问题的(O(nlogn))解法(不严格),同样可以拓展至此题。
在排列中,(a)中的每一个元素唯一对应(b)中的一个元素,但在一般的LCS问题中不是这样,一个元素可以对应多个元素。
怎么办呢?我们把(a)中每个元素在(b)中对应位置的集合拿出来,比如(a={3,3,2,4},b={2,3,3,5}),那么(a)中元素(3)对应的位置集合就是{2,3},将每个元素对应位置降序排列,再放回原序列中,对得到的新序列做一个LIS,就是最长公共子序列的长度,比如上面那个例子,a中每个元素对应集合为({2,3},{2,3},{1},{}),得到的新序列就是(3,2,3,2,1),它的最长上升子序列是(2,3),即(a,b)的最长公共子序列是由(b_2,b_3)组成的,这时再套用LIS的(O(nlogn))做法即可,但是这样复杂度不是严格(O(nlogn))的,因为每个(a)中的元素最多对应(n)个b中的元素,最坏复杂度达到(O(n^2 log n))。
至于这种做法到上面这题的拓展,因为你要保证你在(a)中选择的元素在(b)中的位置是递增的,同时要保证这些元素的值本身也是递增的,也就是这样:
发现是转移一个二维偏序关系,二维数点即可,复杂度是不严格的(O(nlog^2 n))。