序言
套路是人类进步的阶梯,我将不惜一切代价套路学习!
—— 费清澄
恩恩真是太对了
题目描述
zqc是一只套路的犰♂
zqc有一个套路题库,当然,他为了让这个套路题库不被发现,给题库加了密。这个题库有很多密码,你只有输入套路密码后,你才能看到题目,并且题目的质量和套路密码的长度成正比.根据你获取到的情报,套路密码为两个字符串的公共子序列,现在你想知道最长套路密码的长度,以便获得高质量的套路题目.
对于所有的数据满足|S| ≤ 1e6 , |T| ≤ 1e3 , 字母均为小写字母
其实就是求最长公共子序列啦.
显然最长公共子序列有O(nm)的DP做法,但是显然,这里nm在1e9数量级,这个方法是行不通的.
车到山前必有路,有路必有老司机,这种题目,要么是有比原来更加优秀的最长公共子序列求法,要么就要利用题目本身的性质.
事实上,确实有这么一种不同于原来O(nm)的DP的算法,复杂度在O(nlogn)~O(nmlogn)之间.大致思路就是推广了一下两个串都是全排列时的情况,使其可以解决一般字符串的问题.
定理
设序列A长度为n,{A(i)},序列B长度为m,{B(i)},考虑A中所有元素在B中的序号,即A某元素在B的序号为{Pk1,Pk2,..},将这些序号按照降序排列,然后按照A中的顺序得到一个新序列,此新序列的最长严格递增子序列即对应为A、B的最长公共子序列。
很明显的可以看出,这个算法的复杂度和原序列的字符稠密度有关,假如只有一个字符,那么复杂度就会退化到O(n²logn),但是如果全都不同,那就是O(nlogn)(此时就是全排列求LIS).
那么平均复杂度(假设每种字符出现频率一样)大概就是O(nm/p*logn),其中p是n和m中都出现过的字符个数.
#include#include #include <string.h> #include #include <string> #include #include #include #include using namespace std; const int maxn = 200000001 ; vector<int> location[26] ; int c[maxn] , d[maxn] ; char a[maxn] , b[maxn] ; inline int get_max(int a,int b) { return a > b ? a : b ; } //nlogn 求lcs int lcs(char a[],char b[]) { int i , j , k , w , ans , l , r , mid ; for( i = 0 ; i < 26 ; i++) location[i].clear() ; for( i = strlen(b)-1 ; i >= 0 ; i--) location[b[i]-'a'].push_back(i) ; for( i = k = 0 ; a[i] ; i++) { for( j = 0 ; j < location[w=a[i]-'a'].size() ; j++,k++) c[k] = location[w][j] ; } d[1] = c[0] ; d[0] = -1 ; for( i = ans = 1 ; i < k ; i++) { l = 0 ; r = ans ; while( l <= r ) { mid = ( l + r ) >> 1 ; if( d[mid] >= c[i] ) r = mid - 1 ; else l = mid + 1 ; } if( r == ans ) ans++,d[r+1] = c[i] ; else if( d[r+1] > c[i] ) d[r+1] = c[i] ; } return ans ; } int main() { freopen("string.in","r",stdin); freopen("string.out","w",stdout); while (~scanf("%s%s",a,b)) { printf("%d\n",lcs(a,b)); } }
如果不开Subtask的话,可以通过大约70%的数据.
显然这不是正解,因此我们需要换个思路.
那就需要考虑题目的特殊性质.我们可以看出,小的字符串长度只有1e3,我们可以利用这个性质.显然最长公共子序列的长度不会超过小的序列的长度,所以我们可以考虑进行如下的DP.
不妨令S表示长的序列,T表示短的序列,设dp[i][j]表示T的前i位和S的最长公共子序列的最后一位最小是多少,设对了状态就很容易做了,下面给出代码.
#include#include #include #include #include #include #include
复杂度为O(m²logn+n)