LCIS 最长上升公共子序列问题
首先点名一个串叫 L1,另一个叫L2。
明显的是一个DP,那么我们来探讨下如何求得答案。
朴素的算法
首先我们定义状态$dp[ i ][ j ]$表示L1中前i个与L2中前j个的最长公共上升子序列。
最外层枚举i,第二层枚举j,那么L1[i]和L2[j]要么相等,要么不相等,分情况讨论。
■ L1[i]=L2[j] 那么我们以当前L1[i]的大小作为某个公共上升子序列的结尾元素,所以我们就需要向前找比这个结尾元素小的元素来接头,就需要找到dp[i-1][k] $(k<j&&a[k]<a[j])$中的最长的公共上升子序列的长度,然后其数列长度+1,就是现在dp[i][j]的值。
■ L[1]!=L2[j]那么这时候我们则需要继承前边最长的序列长度$dp[i][j]=max(dp[i-1][1->j-1])$.
这样的时间复杂度$O(n^2 \times m)$,空间复杂度$O(n \times m)$,比较朴素的一中做法。
代码自行脑补吧。。。。
优化
我们来优化一下上边的算法。
我们每个数在计算的时候,这一行前边的数我们已经都计算过了,在这之间我们完全可以用maxn记录一下最大值,避免再去循环枚举k,直接用maxn就好了。
时间复杂度$O(n \times m)$
void LCIS(){ memset(F,0,sizeof(F)); for(int i=1;i<=n;i++){ LL maxn = 0; for(int j=1;j<=m;j++){ F[i][j] = F[i-1][j]; if(a[i]>b[j] && maxn < F[i-1][j]) maxn = F[i-1][j]; if(a[i] == b[j]) F[i][j] = maxn+1; } } LL maxn = 0; for(int j=1;j<=m;j++){ if(maxn < F[n][j]) maxn = F[n][j]; } printf("%lld\n",maxn); }
空间复杂的也可以通过压维进行优化。
对于每次dp[i][j]的改变我们只需要dp[i-1][1->j-1]中的最大值,压维以后,我们只需要dp[i][1->j-1]中的最大值,同样maxn记录就好了。
你可能会问:
每次我们查询后边的都会用到前边的,前边的改变后对后边的不会有影响么?
不会,因为我们只需要最大值,在这个数改变之前,进行最大值的替换,当它改变后对后边计算没有任何影响。
#include <cstdio> #include <cstdlib> #include <cstring> using namespace std; int T,n,m,f[100006],a[100006],b[100006],ans; int max(int a,int b){ return a>b?a:b; } int main() { freopen("codes.in","r",stdin); freopen("codes.out","w",stdout); scanf("%d",&T); while(T--) { ans=0; 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]); memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) { int maxn=0; for(int j=1;j<=m;j++) { if(a[i]>b[j])maxn=max(maxn,f[j]); if(a[i]==b[j])f[j]=maxn+1; ans=max(ans,f[j]); } } printf("%d\n",ans); } fclose(stdin);fclose(stdout); }
除特别注明外,本站所有文章均为Manjusaka丶梦寒原创,转载请注明来自出处