最长公共子序列
该题解参考这位博主https://blog.csdn.net/weyuli/article/details/9309121,我写这篇随笔只是为了解释一下这位博主写的代码,因为代码的注释很少,若是有跟我一样的新人怕是要耗费很长时间去理解题意,借此机会说说我对这个题的思路,并解释该博主的部分代码
内容:
从一个给定的串中删去(不一定连续地删去)0个或0个以上的字符,剩下地字符按原来顺序组成的串。例如:“ ”,“a”,“xb”,“aaa”,“bbb”,“xabb”,“xaaabbb”都是串“xaaabbb”的子序列。(例子中的串不包含引号。)
编程求N个非空串的最长公共子序列的长度。限制:2<=N<=100;N个串中的字符只会是数字0,1,…,9或小写英文字母a,b,…,z;每个串非空且最多含100个字符;N个串的长度的乘积不会超过30000。
此题的理解还需了解如何求两字符序列的最长公共字符子序列,若不懂请自行百度学习, 题目练习题号SDUT 2080。
题目中有一句话:N个串的长度的乘积不会超过30000,这个地方告诉了我们数据的规模并且还强调了N个串长度的乘积,这里就要提一下,在求两字符序列的最长公共字符子序列这个问题中,我们为了方便理解,会画一个m * n的表格(m代表字符串x的长度,n代表字符串y的长度,x,y为任意两字符序列)来观察公共字符个数,两字符序列的最长公共字符子序列问题还可以看成二维公共子序列问题,因为解题的时候我们会建立一个二维数组。
该题可以看成一个多维字符求最长公共子序列,但是题目说:N个子符串的乘积不会超过30000。这里我们就可以把这道多维问题转化为一个一维问题,参照求两字符序列的最长公共字符子序列问题把本该建立的多维表格给转化为一维数组,数组的下标的含义可以看成:index = x1 * x2 * x3 * …… * xi。每一个index都对应着唯一的情况,即:多维字符串的长度为x1,x2,x3……xi时是否有公共字符。
具体思路如下:
建立一个二维字符数组保存字符,建立一个一维数组len来保存每一个字符数组的长度,一维数组L来保存多维字符数组长度的动态变化,用于求index(这部分请结合下面的代码利于理解),我们从每一个字符数组的末尾开始匹配,若都相等则把每一个多维数组长度L减一(所以我称它为动态长度数组),所以最长长度ret = DP(L) + 1;若只要有一个不同,则开始枚举每一种情况,详细请看代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; char a[110][110]; int dp[30000 + 10]; int len[110]; int n; int DP( int *x ) { int i, j, index, ret; for( i = 1; i <= n; i++ ) { if( x[i] == 0 ) { return 0; } } for( index = x[n] - 1, i = n - 1; i >= 1; i-- ) { index = index * len[i] + x[i] - 1; //转化为一维数组 } if( dp[index] >= 0 ) { return dp[index]; } //从每个字符串的末尾开始匹配 for( i = 2; i <= n; i++ ) { if( a[1][x[1] - 1] != a[i][x[i] - 1] ) { break; } } if( i > n ) { for( j = 1; j <= n; j++ ) { x[j]--; } ret = DP(x) + 1; for( j = 1; j <= n; j++ ) { x[j]++; } } else { ret = 0; int t; //开始枚举每一种情况 for( j = 1; j <= n; j++ ) { x[j]--; t = DP( x ); ret = max( ret, t ); x[j]++; } } dp[index] = ret; return ret; } int main() { int T; int L[110]; scanf("%d", &T); while( T-- ) { scanf("%d", &n); for( int i = 1; i <= n; i++ ) { scanf("%s",a[i] ); len[i] = L[i] = strlen( a[i] ); } memset( dp, -1, sizeof(dp) ); printf("%d\n", DP( L ) ); } return 0; } |