3510. 最长公共子序列
首先我是菜鸡,写的不好勿喷
传送门、
给出两个长度为n的整数序列,求它们的最长公共子序列(LCS)的长度,保证第一个序列中所有元素都不重复。
注意:
- 第一个序列中的所有元素均不重复。
- 第二个序列中可能有重复元素。
- 一个序列中的某些元素可能不在另一个序列中出现。
输入格式
第一行包含一个整数n。
接下来两行,每行包含n 个整数,表示一个整数序列。
输出格式
输出一个整数,表示最长公共子序列的长度。
数据范围
1≤n≤106,
序列内元素取值范围[1,1e6]
06
输入样例1:
5
1 2 3 4 5
1 2 3 4 5
输出样例1:
5
输入样例2:
5
1 2 3 5 4
1 2 3 4 5
输出样例2:
4
这个题一看标题这不是一个板子题吗,一看数据范围wokao?
然后你仔细读一下题意就是序列a的是没有重复的,也就是说第二个序列中的元素在第一个里面的下标是唯一的,
我们得在这里做一点文章
我们可以先把a中的每一个数都映射成为它的下标,就是id[a[i]]=i;然后我能定义一个 f 数组,就是对于b数组选 i 的最大值
然后我们遍历b数组,对于b数组f[i]=max(f[i],max(f[j]))这个j属于f[1,vis[b[i]]-1]
然后写了一个假算法
#include<iostream> #include<algorithm> using namespace std; const int maxn=1e6+100; int a[maxn],b[maxn]; int vis[maxn]; int dp[maxn]; int main(){ int n; cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; vis[a[i]]=i; } for(int i=1;i<=n;i++){ cin>>b[i]; } int ans=0; int p=0; for(int i=1;i<=n;i++){ if(vis[b[i]]){ dp[i]=1; } if(!vis[b[i]]) continue; for(int j=1;j<i;j++){ if(vis[b[i]]>vis[b[j]]){ dp[i]=max(dp[i],dp[j]+1); } } } int ma=0; for(int i=1;i<=n;i++){ ma=max(ma,dp[i]); } cout<<ma<<endl; }
很显然这个复杂度是一定要T的,然后就是优化,我是用的线段树维护区间最大值来优化的,
#pragma GCC optimize(2) #include<iostream> #include<algorithm> using namespace std; const int maxn=5e6+100; int a[maxn],b[maxn]; int vis[maxn]; int dp[maxn]; int c[maxn]; struct node{ int l,r; int ma; }t[maxn]; void push(int p){ t[p].ma=(t[2*p].ma,t[2*p+1].ma); } void build(int p,int l,int r){ t[p].l=l; t[p].r=r; t[p].ma=0; if(l==r){ t[p].ma=0; return ; } //线段树 int mid=(l+r)/2; build(2*p,l,mid); build(2*p+1,mid+1,r); push(p); } //线段树 void update(int p,int pos,int k){ if(t[p].l==t[p].r){ t[p].ma=k; return ; } if(pos<=t[2*p].r){ update(2*p,pos,k); } else{ update(2*p+1,pos,k); } t[p].ma=max(t[2*p].ma,t[2*p+1].ma); } int query(int p,int l,int r){ if(t[p].l>=l&&t[p].r<=r){ return t[p].ma; } int mid=(t[p].l+t[p].r)/2; int ans=0; if(l<=mid){ ans=max(ans,query(2*p,l,r)); } if(r>mid){ ans=max(ans,query(2*p+1,l,r)); } push(p); return ans; } int main(){ int n; cin>>n; build(1,1,n+10); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); vis[a[i]]=i; } for(int i=1;i<=n;i++){ scanf("%d",&b[i]); } int ans=0; int p=0; for(int i=1;i<=n;i++){ if(vis[b[i]]==0){ continue; } if(vis[b[i]]){ dp[i]=1; } if(vis[b[i]]!=1) dp[i]=max(dp[i],query(1,1,vis[b[i]]-1)+1); update(1,vis[b[i]],dp[i]); } int ma=0; for(int i=1;i<=n;i++){ ma=max(ma,dp[i]); } cout<<ma<<"\n"; } /* 10 1 4 9 2 7 10 8 3 5 6 7 6 10 7 8 1 6 4 8 4 */
然后这个也可以用二分,这个我没有看出来,然后如果说二分可以解决还是用二分把,这个线段树的代码太长了,还容易错,
然后看了y总的视频讲解,真是太巧妙了,y总yyds
总结了一些y总的思路,就是在某一些条件下最长上升子序列和最长公共子序列可以相互转化的,条件就是某一个序列的元素不重复
就是这样。
然后这个题就转化为求b’的最长上升序列
#include<iostream>
#include<algorithm> using namespace std; const int N=1e6+10; int a[N],b[N],idx[N]; vector<int> aa; int main(){ int n; cin>>n; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); idx[a[i]]=i; } for(int i=1;i<=n;i++){ scanf("%d",&b[i]); b[i]=idx[b[i]]; } for(int i=1;i<=n;i++){ if(b[i]==0) continue; if(!aa.size()||b[i]>aa.back()) aa.push_back(b[i]); int id=lower_bound(aa.begin(),aa.end(),b[i])-aa.begin(); aa[id]=b[i]; } cout<<aa.size(); return 0; }