最长公共子序列(LCS)
给出两序列 v 1 , v 2 v1,v2 v1,v2,求它们最长公共子序列长度(子序列可以不连续)
长度问题
DP( O ( n 2 ) O(n^2) O(n2))
闫氏DP分析法
注: v 1 , v 2 v1,v2 v1,v2下标从 0 0 0开始, d p dp dp下标从1开始
-
状态表示
- 集合: d p [ i ] [ j ] dp[i][j] dp[i][j]:表示以 [ 1 , i ] [1,i] [1,i]区间内的 v 1 v1 v1与 [ 1 , j ] [1,j] [1,j]区间内的 v 2 v2 v2最长公共子序列长度, i n i t ( d p [ i ] [ 0 ] , d p [ 0 ] [ i ] ) = 0 init(dp[i][0],dp[0][i])=0 init(dp[i][0],dp[0][i])=0
- 属性: M a x Max Max
-
状态计算
- 当 v 1 [ i ] = v 2 [ j ] v1[i]=v2[j] v1[i]=v2[j]时:继承自 L C S ( v 1 [ i − 1 ] , v 2 [ j − 1 ] ) LCS(v1[i-1],v2[j-1]) LCS(v1[i−1],v2[j−1]),并加1。 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i-1][j-1]+1 dp[i][j]=dp[i−1][j−1]+1
- 当
v
1
[
i
]
≠
v
2
[
j
]
v1[i]\ne v2[j]
v1[i]=v2[j]时:
- 考虑 v 1 v1 v1回退一步:继承自 L C S ( v 1 [ i − 1 ] , v [ j ] ) LCS(v1[i-1],v[j]) LCS(v1[i−1],v[j])。 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i−1][j]
- 考虑 v 2 v2 v2回退一步:继承自 L C S ( v 1 [ i ] , v 2 [ j − 1 ] ) LCS(v1[i],v2[j-1]) LCS(v1[i],v2[j−1])。 d p [ i ] [ j ] = d p [ i ] [ j − 1 ] dp[i][j]=dp[i][j-1] dp[i][j]=dp[i][j−1]
-
状态转移方程式:
dp[i][j] =
\left\{
\begin{array}\\
dp[i-1][j-1]+1 & ,v1[i]=v2[j] \\
max(dp[i-1][j],dp[i][j-1]) & ,\text{v1[i]}\ne \text{v2[j]}
\end{array}
\right.
extern vector<int>v1,v2;//v1 v2下标从0开始
extern vector<vector<int>>dp(v1.size()+1,vector<int>(v2.size()+1));//dp下标从1开始
int lcs(){
for(int i=1;i<=v1.size();i++)
for(int j=1;j<=v2.size();j++)
if(v1[i-1]==v2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
return dp[v1.size()][v2.size()];
}
滚动数组优化
- 交替滚动
extern vector<int>v1,v2;//v1 v2下标从0开始
extern vector<vector<int>>dp(2,vector<int>(v2.size()+1));//dp下标从1开始
int lcs(){
int work=1,old=0;
for(int i=1;i<=v1.size();i++){
swap(old,work);
for(int j=1;j<=v2.size();j++)
if(v1[i-1]==v2[j-1]) dp[work][j]=dp[old][j-1]+1;
else dp[work][j]=max(dp[old][j],dp[work][j-1]);
}
return dp[work][v2.size()];
}
- 自我滚动
思路:定义变量 o l d old old存储 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i−1][j−1],且每次更新
extern vector<int>v1,v2;//v1 v2下标从0开始
extern vector<int>dp(v2.size()+1);//dp下标从1开始
int lcs(){
for(int i=1;i<=v1.size();i++){
int old=dp[0];//dp[0]:dp[i-1][j-1]
for(int j=1;j<=v2.size();j++){
int temp=dp[j];//存储更新前的dp[j]
if(v1[i-1]==v2[j-1]) dp[j]=old+1;
else dp[j]=max(dp[j],dp[j-1]);//dp[j]:dp[i-1][j],dp[j-1]:dp[i][j-1]
old=temp;//将更新前的dp[j](即dp[i-1][j]赋给old,即为下一轮的dp[i-1][j-1])
}
}
return dp[v2.size()];
}
最长不上升子序列求解LCS( O ( n log 2 n ) O(n\log_2n) O(nlog2n))
算法流程:设序列 v 1 , v 2 v1,v2 v1,v2,任选其中一个序列作为标准序列(此处以 v 1 v1 v1为例),用一种特殊的离散化技巧,对 v 1 v1 v1按下标进行离散化,并归位到序列 v 2 v2 v2中,通过求 v 2 v2 v2的最长不上升子序列,即为 L C S ( 原 v 1 , 原 v 2 ) LCS(原v1,原v2) LCS(原v1,原v2)
注:此处必须求最长不上升子序列而非 L I S LIS LIS,因为需要考虑 v 1 v1 v1有重复元素情形
extern vector<pair<int,int>>v1,v2;//v1 v2下标从0开始,first代表原数据,second代表离散化后的数据
extern vector<int>d;
bool cmp(pair<int,int>a,pair<int,int>b){
return a.first<b.first;
}
int lcs(){
for(int i=0;i<n;i++) v1[i].second=i+1;//对v1进行离散化
sort(v1.begin(),v1.end(),cmp);//排序,为了对v2归位能够二分加速
for(auto &i:v2) i.second=lower_bound(v1.begin(),v1.end(),i,cmp)->second;//对v2进行归位
for(auto i:v2){
if(d.empty()){
d.push_back(i.second);
continue;
}
if(i.second>=d.back()) d.push_back(i.second);
else *lower_bound(d.begin(),d.end(),i.second)=i.second;
}
return d.size();
}