LIS、LCS、LCIS
最长上升子序列LIS
题目链接:AcWing895. 最长上升子序列
这里只说明
O
(
n
2
)
O(n^2)
O(n2)的解法,
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)解法之前的博客有介绍
O
(
n
2
)
O(n^2)
O(n2)的解法较为容易理解
状态表示:
这里状态用一维表示,dp[i]表示以b[i]作为结尾的最长子序列的长度
状态转移:
遍历b[i]前的所有元素,找出最长的以b[i]结尾的最长子序列长度
for(int j=0;j<i;j++)
if(b[j]<b[i]) dp[i]=max(dp[i],dp[j]+1)
代码如下
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int b[N];
int dp[N];
int main(){
int n,res=0;
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
b[0]=-0x3f3f3f3f;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++)
if(b[i]>b[j]) dp[i]=max(dp[i],dp[j]+1);
res=max(res,dp[i]);
}
cout<<res;
return 0;
}
最长公共子序列LCS
题目链接:AcWing897. 最长公共子序列
状态表示:
状态用二维表示,dp[i][j]表示A[1~i],B[1~j]的最长公共子序列
状态转移:
if(A[i]==B[j]) dp[i][j]=dp[i-1][j-1]+1;//最长公共子序列增长一位
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);//从两种情况中选择长的一种传递
代码如下
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int dp[N][N];//dp[i][j]表示A[1~i]与B[1~j]最长公共子序列
char A[N],B[N];
int main(){
int n,m;
cin>>n>>m;
cin>>A+1>>B+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(A[i]==B[j]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
cout<<dp[n][m];
return 0;
}
最长公共上升子序列LCIS
AcWing272. 最长公共上升子序列
这里我们考虑将上面的LIS和LCS结合
状态表示:
状态用二维来表示
dp[i][j]表示A[1~i]与B[1~j]且以B[j],结尾的最长公共上升子序列长度
状态转移:
if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
else {
int t=0;
for(int k=1;k<j;k++)
if(B[k]<B[j]) t=max(t,dp[i][k]);
dp[i][j]=t+1;
}
当A[i]!=B[j]时,状态从dp[i-1][j]转移来
当A[i]==B[j]时,我们要考虑将B[j]前面求出的最长公共上升子序列中的哪个子序列之后,所以需要遍历前面所有的子序列(A[i]与B[1~k],k<j匹配的子序列)
代码如下
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=3010;
int A[N],B[N];
int dp[N][N];//dp[i][j]表示A[1~i]与B[1~j]且以B[j],结尾的最长公共上升子序列
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&A[i]);
for(int i=1;i<=n;i++) scanf("%d",&B[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
else {
int t=0;
for(int k=1;k<j;k++)
if(B[k]<B[j]) t=max(t,dp[i][k]);
dp[i][j]=t+1;
}
}
int res=0;
for(int i=1;i<=n;i++)
res=max(res,dp[n][i]);
cout<<res;
return 0;
}
我们可以发现这里的时间复杂度为
O
(
n
3
)
O(n^3)
O(n3),能处理的数据范围很小
我们可以对上面的代码进行优化,
其中
if(B[k]<B[j]) t=max(t,dp[i][k]);
此处的B[j]==A[i],所以可以写成
if(B[k]<A[i]) t=max(t,dp[i][k]);
我们要找到最大的B[k],可以提前将max(B[k])求出来
将下面代码优化
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
else {
int t=0;
for(int k=1;k<j;k++)
if(B[k]<B[j]) t=max(t,dp[i][k]);
dp[i][j]=t+1;
}
}
可以在遍历B[1~j]中求出B[k]<A[i]时的最大的dp[i][k]
for(int i=1;i<=n;i++){
int t=0;
for(int j=1;j<=n;j++){
if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
else dp[i][j]=t+1;
if(A[i]>B[j]) t=max(t,dp[i][j]);
}
}
代码如下
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=3010;
int A[N],B[N];
int dp[N][N];//dp[i][j]表示A[1~i]与B[1~j]且以B[j],结尾的最长公共上升子序列
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&A[i]);
for(int i=1;i<=n;i++) scanf("%d",&B[i]);
for(int i=1;i<=n;i++){
int t=0;
for(int j=1;j<=n;j++){
if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
else dp[i][j]=t+1;
if(A[i]>B[j]) t=max(t,dp[i][j]);
}
}
int res=0;
for(int i=1;i<=n;i++)
res=max(res,dp[n][i]);
cout<<res;
return 0;
}