重温dp——最长上升公共子序列
一道经典的dp了
题目描述
给出 1,2,…,n 的两个排列 P1 和 P2 ,求它们的最长公共子序列。
输入格式
第一行是一个数 n。
接下来两行,每行为 n 个数,为自然数 1,2,…,n 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
值得记录的原因是它可以转化,这个巧妙的转化我觉得是应该学习的
对于一个子串,我们将他转化
关于为什么可以转化成LIS问题,这里提供一个解释。
A:3 2 1 4 5
B:1 2 3 4 5
我们不妨给它们重新标个号:把3标成a,把2标成b,把1标成c……于是变成:
A: a b c d e
B: c b a d e
这样标号之后,LCS长度显然不会改变。但是出现了一个性质:
两个序列的子序列,一定是A的子序列。而A本身就是单调递增的。
因此这个子序列是单调递增的。
换句话说,只要这个子序列在B中单调递增,它就是A的子序列。
哪个最长呢?当然是B的LIS最长。
自此完成转化。
来自luogu大佬的通俗易懂解释
这个能够转化的原因就是当我们在考虑最长公共子序列的时候,每一个数字的大小其实已经失去了意义,而有意义的是他们是否相同和他们的先后顺序。
在我们替换之后,相同的数字自然是也要继续相同的,那么我们替换的目的其实就是把不好处理的两个序列的先后顺序进行简化。
那么将一个序列替换使其完全有序,那我们不就不需要考虑它了吗?
于是问题就被简化了。
我认为这个问题应该这样考虑,当然直接根据做题的经验得到做法也可以,但是这样分析应该会更加的方便和通用吧?应该
#include<bits/stdc++.h> #define ll long long using namespace std; inline int read() { char c=getchar();int a=0,b=1; for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1; for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b; } ll n,m[5000001],a[5000001],b[5000001]; ll f[5000001],len; int main() { n=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++)b[i]=read(); for(int i=1;i<=n;i++)m[a[i]]=i; memset(f,2147483647,sizeof(f)); f[1]=m[b[1]];len=1; for(int i=2;i<=n;i++) { if(m[b[i]]>f[len])f[++len]=m[b[i]]; else { int l=0,r=len; while(l<r) { int mid=(l+r)/2; if(f[mid]>m[b[i]])r=mid; else l=mid+1; } f[l]=min(f[l],m[b[i]]); } } cout<<len<<endl; return 0; }