好题,一道题目复习了多个知识点
首先一看就是lcs,可以用n*n的dp来做,但一看数据量,n可以到10000,虽然空间可以优化,但绝对要超时。于是就只能先把lcs 转化成lis再用nlgn的算法求出来。
首先,将lcs转化成lis:
将s1中的数字在s2中出现的位置记下来并由大到小排列,然后用排列后的数组替换s1中该数字出现的所有位置,这题各个数字都是不同的,所以做着非常方便,注意如果数字重复很多,这样就可能退化成n*n的。这样做可行的原理就是,将对应的位置存下来之后,只要是s1中的数字,在s2中都有对应,且对于现在的s1来说,这些数字对应的原来的数字的出现顺序是上升的,然后只要求这些数字的lis,就得到了对应于s2的序列,且这个子序列在s2中也是按位置上升的,于是对应的序列刚好就是lcs。
然后nlgn lis求法:
用s1中的元素维护一个栈,先使栈中的元素构成递增序列,这时,栈的大小就是该递增序列的长度,牢记这一点,栈的大小是递增序列的长度且我们只关心它的长度而不关心内容。然后依次遍历s1中的元素,如果元素比栈顶元素大,直接入栈,这个很好理解;如果小于栈顶元素,就到栈中二分查找大于它的第一个元素,并替换掉,这时,栈中的递增系列虽然不是原s1中的lis,但栈的大小依然是刚才得到的最大的递增序列的长度,只是插入的这个元素,为将来可能更长的lis做好了准备。
最后就是二分查找的写法,注意mid=front+(rear-front)/2搭配front<rear和front=mid+1,相对来说比较简洁
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define MAX 63000
using namespace std;
int n,p,q,s[MAX],s1[MAX],s2[MAX],mapt[MAX],stackt[MAX],indext,sp;
int bs(int value){
int frontt=0,rear=sp;
while(frontt<rear){
int mid=frontt+(rear-frontt)/2;
if(stackt[mid]<value)
frontt=mid+1;
else
rear=mid;
}
return frontt;
}
int findt(){
sp=0;
for(int i=0;i<indext;i++){
if(sp==0||sp>0&&s[i]>stackt[sp-1])
stackt[sp++]=s[i];
else if(s[i]<stackt[sp-1]){
int ret=bs(s[i]);
stackt[ret]=s[i];
}
}
return sp;
}
int main(){
int T,cases=0;
cin>>T;
while(T--){
memset(mapt,-1,sizeof(mapt));
indext=0;
cin>>n>>p>>q;
for(int i=0;i<=p;i++){
cin>>s1[i];
mapt[s1[i]]=i;
}
for(int i=0;i<=q;i++){
cin>>s2[i];
if(mapt[s2[i]]!=-1)
s[indext++]=mapt[s2[i]];
}
cout<<"Case "<<++cases<<": "<<findt()<<endl;
}
return 0;
}