《训练指南》——7.25
Uva10635:
有两个长度为p+1,q+1的序列A、B,每个序列的各个元素互不相同,且都是1~n^2的整数。两个序列的第一个元素均为1.求出A和B的最长公共子序列长度。
分析:乍一看是典型的LCS问题,但是我们一考察数据发现n = 250 * 250结合LCS问题O(pq)的时间复杂度,使得这个问题需要必要的优化技巧。
我们从题目描述出发,这里有一个很关键的要素是,两个序列各个序列中的元素互不相同,因此每个数据和其位置坐标将形成一一对应的关系,那么我们用num1[i]记录A序列中整数i的下标, 同时在输入B序列时,我们用num2[i]表示下标为i的整数x在A序列中的下标ai。那么现在我们考察序列num2[],我们会得到如下两组序列:
x0 x1 x2 x3 …xi...
a0 a1 a2 a3 …ai...
1 2 3 4 … i...
其含义为,在A、B中共有的元素xi在序列A中的下标是ai , 在序列B中的下标是i。做这样一个复杂的数组的意义到底是什么呢?我们会发现,基于LCS的定义,联系下标,我们能够发现,公共子序列C中的各个元素在序列A、B中的下标序列均为是严格的递增,那么回到这个问题来,元素在序列B中的下标是1,2,3,4…显然是严格递增的了,也就是说,所谓的LCS问题,由于这道问题的特殊限制(每个序列的各个元素互不相同),我们可以将LCS问题转化成num2[]数组的LIS问题,而处理LIS问题我们有O(nlogn)的算法,而制作num1、num2两个数组的时间复杂度几乎是忽略不计的(伴随输入过程进行构造)。
简单的参考代码如下。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn = 250 * 250; int S[maxn] , g[maxn] , dp[maxn]; int num[maxn]; const int INF = 1000000000; int main() { int t; int tt = 1; scanf("%d",&t); while(t--) { int n , p , q , x; scanf("%d%d%d",&n,&p,&q); for(int i = 1;i <= p + 1;i++) { scanf("%d",&x); num[x] = i; } int index = 0; for(int i = 1;i <= q + 1;i++) { scanf("%d",&x); if(num[x]) S[index++] = num[x]; } for(int i = 0;i < index;i++) g[i] = INF; int Max = 0; for(int i = 0;i < index;i++) { int k = lower_bound(g + 1 , g + index + 1 , S[i]) - g; dp[i] = k; g[i] = S[i]; Max = max(Max , dp[i]); } printf("Case %d: %d\n",tt,Max); tt++; } }