又是模板题呵,但这次的难度有点增加。
先看题目第一个想到DP的经典算法,要O(n^2),然后想其它的算法。
其实我们衢州市一次联考有一题很像这题,不过还要难一点。
思想是离散化+最长不下降子序列(在这里和最长上升子序列等价,因为没有重复的值)
先离散一下第二串里每个点的第一串里的位置(数组也可以,但我喜欢用map),如样例:
5
3 2 1 4 5
1 2 3 4 5
散出来就是 3 2 1 4 5,然后为了使他们公共最长,想到什么?
最长不下降子序列!为什么?
刚开始做的是根据他们的数值,但现在将他们转化成了标号(important)
对于第一串的标号,很简单,就是1~n(自己对应自己的位置)
然后剩下的就是在第二串里找最长的递增作为它的重复部分了。
如果裸的最长不下降子序列(DP),复杂度也是O(n^2),贴一下代码,但道理应该大多数OI的书上都有
50CODE
#include<cstdio>
#include<map> using namespace std; inline void read(int &x) { x=0; char ch=getchar(); while (ch<'0'||ch>'9') ch=getchar(); while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); } inline int max(int a,int b) { return a>b?a:b; } const int N=100005; map <int,int> ma; int n,x,num[N],f[N],ans,i,j; int main() { read(n); for (i=1;i<=n;++i) read(x),ma[x]=i; for (i=1;i<=n;++i) read(x),num[i]=ma[x]; for (i=1;i<=n;++i) { f[i]=1; for (j=1;j<i;++j) if (num[j]<num[i]) f[i]=max(f[i],f[j]+1),ans=max(ans,f[i]); } printf("%d",ans); return 0; }
然后优化。方法有两种——二分或树状数组。
个人还是感觉二分的简单些(因为不会打树状数组的版本)
用top表示最长不下降子序列的长度;表示用f[i]表示i长度的结尾元素的值。
仔细观察一下,发现他们满足f[1]<=f[2]<=f[3]……
然后就可以进行二分优化了
CODE(常数还挺小)
#include<cstdio> #include<map> using namespace std; const int N=100005; map <int,int> ma; int n,x,num[N],f[N],i,top; inline void read(int &x) { x=0; char ch=getchar(); while (ch<'0'||ch>'9') ch=getchar(); while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); } inline int max(int a,int b) { return a>b?a:b; } inline int find(int x) { int left=1,right=top,res=0; while (left<=right) { int mid=left+right>>1; if (f[mid]<x) res=mid,left=mid+1; else right=mid-1; } return res; } int main() { read(n); for (i=1;i<=n;++i) read(x),ma[x]=i; for (i=1;i<=n;++i) read(x),num[i]=ma[x]; for (i=1;i<=n;++i) { if (!top) { f[++top]=num[i]; continue; } else { if (num[i]<f[1]) f[1]=num[i]; int k=find(num[i]); top=max(top,k+1); f[k+1]=num[i]; } } printf("%d",top); return 0; }