题意:有n*n的格子用1-n*n不同的数表示,第一个人输入p+1长度的数,第二个人输入q+1个不同的数,问他们最长公共子序列最长是多长。
分析:由于(0<=n<=250,n*n最大=250*250),用O(n^2)的算法是不能解决的,需要一种nlog(n)的算法。由于这里比较特殊,每个人输入的数都是不重复的,那么就把第一个人输入的数映射成1,2,3……,第二个人输入的数映射对应第一个人输入数的映射。这样,第一个人的映射是递增的,那么我只需要找到第二个人的映射的最长上升子序列的个数就是ans。
那么怎么求这个最长上升子序列呢?假设求1,4,6,3,5,7,最长上升子序列
dp[i]:表示长度为i,最小的那个数
①加入1,dp[1]=1;②加入4,dp[2]=4;③加入6,dp[3]=6;④加入3,dp[2]=3;⑤加入5,dp[3]=5;⑥加入7,dp[4]=7;
所以最终ans=4,这里,应为dp[]里存的数都是单调递增的,因此可以二分查找或lower_bound(加入的数)的id,更新dp[id]。
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
//http://acm.hust.edu.cn/vjudge/contest/view.action?cid=82285#problem/E
#include <stack>
#include <vector>
#include <string>
#include <string.h>
#include <map>
#include <set>
using namespace std;
#define maxn 250*251
#define inff 1000000000
int a[maxn],b[maxn],dp[maxn];
int lower_bound(int aa[],int size,int k)//查找第一个>=k的数的id
{
int l,r,mid;
l=0;r=size-1;
while(l <= r)
{
mid=(l+r)/2;
if(aa[mid] >= k) r = mid - 1;
else l = mid + 1;
}
return l;
}
int main()
{
int t,k,n,num1,num2,i,m,tt=1;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&n,&num1,&num2);
num1++;
num2++;
memset(a,-1,sizeof a);
memset(b,-1,sizeof b);
for(i=1;i<=num1;i++)
{
scanf("%d",&m);
a[m]=i;//把第一个人的数映射成1,2,3……
}
k=1;
for(i=1;i<=num2;i++)
{
scanf("%d",&m);
if(a[m]!=-1)
b[k++]=a[m];//第二个人输入的数,参照第一个人的映射,存下该数的映射
}
for(i=0;i<maxn;i++)
{
dp[i]=inff;
}
dp[0]=-1;//令dp[0]最小
for(i=1;i<k;i++)
{
int j=lower_bound(dp,maxn-1,b[i]);
dp[j]=b[i];
}
for(i=1;i<maxn;i++)
{
if(dp[i]==inff)
{
printf("Case %d: %d\n",tt++,i-1);
break;
}
}
}
return 0;
}