P1439 【模板】最长公共子序列
题意:给出两个序列,求最长公共子长度
思路:第一种直接暴力,代码如下:
1 #include<iostream> 2 using namespace std; 3 const int maxn=1e5+10; 4 int dp[maxn][maxn]; 5 int a1[maxn],a2[maxn],n,m; 6 int main() 7 { 8 //dp[i][j]表示两个串从头开始,直到第一个串的第i位 9 //和第二个串的第j位最多有多少个公共子元素 10 cin>>n>>m; 11 for(int i=1;i<=n;i++)scanf("%d",&a1[i]); 12 for(int i=1;i<=m;i++)scanf("%d",&a2[i]); 13 for(int i=1;i<=n;i++) 14 for(int j=1;j<=m;j++){ 15 dp[i][j]=max(dp[i-1][j],dp[i][j-1]); 16 if(a1[i]==a2[j]) 17 dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1); 18 //因为更新,所以++; 19 } 20 cout<<dp[n][m]; 21 }
但是这种方法在数据到达1e4 、1e5时,就会超时;而暴力方法在转移的时候有许多无用的转移点
所以引入一种二分的方法 把复杂度降低道n*logn
因为本题所有数据都是唯一的,即某个数在某一序列中最多出现一次;所以可以用mp来标记位置;
我们开一个f【】数组,记录的不再是长度,而是在某一长度下的到达的位置的最小值
什么意思呢? 我们有两个序列,我们是拿第二个序列跟第一个序列层层比较的
比如f【1】=2,就是有一个公共子的情况下,跟a序列比较到的最小位置是到第二个位置,即第二个后面的数还没比较过
那么如何二分呢?二分要记录些什么呢?
自然二分要记录的就是,在某一长度下,a序列到达的最小位置,
因为按这一计算方法,f序列递增,所以满足二分的要求
那么二分的时候,我们就找到第一个大于mp【b【j】】的数,为什么一定要这个数呢?
假如是这个数左边的数,那他左边的数肯定小于mp【b【j】】,自然无法做状态转移
假如如果是右边的数,那么如果把这个数强加在右边的数上边的话,就会导致这个位置的长度变小,不满足
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int inf=0x3f3f3f3f; 4 const int maxn=1e5+10; 5 int a[maxn],b[maxn]; 6 int mp[maxn],f[maxn]; 7 int main() 8 { 9 int n; 10 cin>>n; 11 for(int i=1;i<=n;i++){ 12 scanf("%d",&a[i]); 13 mp[a[i]]=i; 14 } 15 for(int i=1;i<=n;i++){ 16 scanf("%d",&b[i]); 17 f[i]=inf; 18 } 19 int len=0; 20 f[0]=0; 21 for(int i=1;i<=n;i++){ 22 int l=0,r=len,mid; 23 if(mp[b[i]]>f[len])f[++len]=mp[b[i]]; 24 else{ 25 while(l<r){ 26 mid=(l+r)/2; 27 if(f[mid]>mp[b[i]])r=mid; 28 else l=mid+1; 29 } 30 f[l]=min(mp[b[i]],f[l]); 31 } 32 } 33 cout<<len; 34 return 0; 35 }