题目大意:求两个均只含有1-N且每个数字都恰好有5个的排列的最长公共子序列。
普通的dp方法时间复杂度为,对本题的数据量来说并不适用。
有这么一个结论:求两个序列的最长公共子序列,等价于求每个位置的数字在另一个序列中的所有位置的逆序组成的序列的最长上升子序列。
举个例子:
序列1:1 1 1 1 1
序列2:1 1 1 1 1
序列1中的每个位置的数字在序列2中所有位置的逆序均为(5,4,3,2,1),则组成的新的位置序列为
5 4 3 2 1 5 4 3 2 1 5 4 3 2 1 5 4 3 2 1 5 4 3 2 1
求出该序列的最长上升子序列为5,即原问题的最长公共子序列为5。
当然,求最长上升子序列也有时间复杂度为的dp方法和二分优化后的方法,本题显然需要后一种方法才能解决。
假设pos数组存新组成的位置序列,dp[i]表示长度为i的结尾数字的最小值,len代表当前的最大长度,易知dp值具有单调性。遍历pos序列,通过二分查找,若当前的pos值大于dp[len],则dp[++len]=pos;否则更新第一个结尾数字大于等于pos的dp值为pos。
稍微思考一下,由上面转换成位置序列的过程可知,两个序列中重复数字的个数越多,最后得到的位置序列数目也就越多,最坏情况是N个数全相同,则位置序列有N*N个数,时间复杂度退化为;最好的情况则是同一个序列中每个数字均不相同,位置序列数也为N,时间复杂度为
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define For(i,a,b) for(int i=a;i<=b;i++)
#define INF 0x3f3f3f3f
#define db double
#define ldb long double
#define m_p make_pair
#define p_b push_back
#define fbo friend bool operator <
const int N = 20005;
const int mod=1e9+7;
struct FastIO//输入挂
{static const int S=200;int wpos;char wbuf[S];FastIO():wpos(0){}inline int xchar(){static char buf[S];static int len=0,pos=0;if(pos==len) pos=0,len=fread(buf,1,S,stdin);if(pos==len) exit(0);return buf[pos++];}inline int read(){int s=1,c=xchar(),x=0;while(c<=32) c=xchar();if(c=='-') s=-1,c=xchar();for(;'0'<=c&&c<='9';c=xchar()) x=x*10+c-'0';return x*s;}~FastIO(){if(wpos) fwrite(wbuf,1,wpos,stdout),wpos=0;}
}io;
int n,a1[5*N],a2[5*N],a[5*5*N],dp[5*5*N];
vector<int> v[N];int main(){
// freopen("1.txt","r",stdin);n=io.read();n=5*n;For(i,1,n) a1[i]=io.read();For(i,1,n) a2[i]=io.read();for(int i=n;i>=1;i--)v[a2[i]].p_b(i);For(i,1,n){For(j,1,5) a[(i-1)*5+j]=v[a1[i]][j-1];}int len=1;dp[len++]=a[1];For(i,2,5*n){int pos=lower_bound(dp+1,dp+len,a[i])-dp;if(pos<len) dp[pos]=a[i];else dp[len++]=a[i];}printf("%d\n",len-1);return 0;
}