LCS + LIS
参考:
https://www.cnblogs.com/chenleideblog/p/10455723.html
https://blog.csdn.net/qq_25800311/article/details/81607168
https://blog.csdn.net/lxt_Lucia/article/details/81206439
一,概念
1,LCS
最长公共子序列(Longest Common Sequence)和最长公共子串(Longest Common Substring)的英文缩写皆为 LCS。
两者的区别在于:
子序列不需要在原序列中占用连续的位置。而最长公共子串要求连续。
2,LIS
最长上升子序列(Longest Increasing Subsequence)
二,最长公共子串
1,推导
有字符串 s1 长度为 n,字符串 s2 长度为 m
设 dp[ i ][ j ] 代表:s1[ 0~i-1 ] 和 s2[ 0~j-1 ] 的最长公共后缀的长度,
即如果 dp[ i ][ j ] 不为 0,则 s1[ 0~i-1 ] 和 s2[ 0~j-1 ] 存在一个公共子串,其最后一个字符为 s1[ i-1 ]。
因为最长公共子串要求连续,所以 dp[ i+1 ][ j+1 ] 只由 dp[ i ][ j ] 和 s[ i ] 和 s[ j ] 决定。
所以有递推式(注意:0 <= i < n,0 <= j < m ):
if s1[ i ] == s2[ j ]
dp[ i+1 ][ j+1 ] = dp[ i ][ j ] + 1
else
dp[ i+1 ][ j+1 ] = 0
所以有递推初始条件:
dp[ 0 ][ 0~m ] = 0;dp[ 0~n ][ 0 ] = 0
2,例题
链接:http://poj.org/problem?id=2217
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 5005 char s1[N], s2[N]; int dp[N][N]; void getLCS() { int n = strlen(s1), m = strlen(s2); int max = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (s1[i] == s2[j]) dp[i + 1][j + 1] = dp[i][j] + 1; else dp[i + 1][j + 1] = 0; if (dp[i + 1][j + 1] > max) max = dp[i + 1][j + 1]; } } printf("Nejdelsi spolecny retezec ma delku %d.\n", max); } int main(void) { int t; scanf("%d", &t); getchar(); while(t--) { scanf("%[^\n]", s1); getchar(); scanf("%[^\n]", s2); getchar(); getLCS(); } system("pause"); return 0; }
三,最长公共子序列
1,推导
有字符串 s1 长度为 n,字符串 s2 长度为 m
设 dp[ i ][ j ] 代表:
s1[ 0~i-1 ] 和 s2[ 0~j-1 ] 的最长公共子序列的长度。
因为 dp[ i ][ j ] 是当前最长的值,所以,如果 s1[ i ] == s2[ j ],则 dp[ i ][ j ] + 1 必然是 s1[ 0~i ] 和 s2[ 0~j ] 的最长公共子序列的长度。
因为子序列不要求连续,所以 dp] i+1 ][ j+1 ] 并不要求其 LCS 以 s[ i ] 或 s[ j ] 结尾,它是包含着 s1[ 0~i-1 ] 和 s2[ 0~j-1 ] 的所有公共子序列中长度最长的一个。
所以,当 s1[ i ] != s2[ j ] 时,dp[ i+1 ][ j+1 ] 不能等于 0,它必须等于它之前的最长子序列。
而 dp[ i+1 ][ j+1 ] 之前的最长子序列长度为:不包括 dp[ i+1 ][ j+1 ] 的 dp[ 0~i+1 ][ 0~j+1 ] 。
而 dp[ i+1 ][ j ] 就代表了 dp[ 0~i+1 ][ 0~j ] 的最大值,dp[ i ][ j+1 ] 就代表了 dp[ 0~i ][ 0~j+1 ] 的最大值。
显然,两者的最大值就是 dp[ i+1 ][ j+1 ] 之前的最长子序列的长度。
所以有递推式(注意:0 <= i < n,1 <= j < m ):
if s1[ i ] == s2[ j ]
dp[ i+1 ][ j+1 ] = dp[ i ][ j ] + 1
else
dp[ i+1 ][ j+1 ] = MAX( dp[ i ][ j+1 ] , dp[ i+1 ][ j ] )
2,例题
链接:http://poj.org/problem?id=1458
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #include<algorithm> using namespace std; #define N 1000+5 char s1[N], s2[N]; int dp[N][N]; void getLCS() { int n = strlen(s1), m = strlen(s2); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) if (s1[i] == s2[j]) dp[i + 1][j + 1] = dp[i][j] + 1; else dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]); } printf("%d\n", dp[n][m]); } int main(void) { while (scanf("%s%s", s1, s2) != EOF) { memset(dp, 0, sizeof(dp)); getLCS(); } system("pause"); return 0; }
3,最长公共子序列的输出
定于一个用于记录的数组 rs,其中 f[ i ][ j ] 代表 dp[ i ][ j ] 是由哪一个值推出来的。即
当 f[ i ][ j ] == 1 时,dp[ i ][ j ] 等于 dp[ i-1 ][ j-1 ] + 1;
当 f[ i ][ j ] == 2 时,dp[ i ][ j ] 等于 dp[ i ][ j-1 ];
当 f[ i ][ j ] == 2 时,dp[ i ][ j ] 等于 dp[ i-1 ][ j ];
因为只有 f[ i ][ j ] == 1 时,s1[ i-1 ] 才会等于 s2[ j-1 ],此时的 s1[ i-1 ] 或 s2[ j-1 ] 才是属于最长公共子序列的。
所以,要想输出 LCS,
就先利用 rs 的标志作用从 f[ n ][ m ] 往前搜索,搜索到 f 数组的行下标为 0 或列下标为 0 为止。
然后在回溯的时候,只输出 f[ i ][ j ] == 1 时,对应的 s1[ i-1 ] 的值,就可以得到 LCS 了。
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 1000+5 char s1[N], s2[N]; int dp[N][N], f[N][N]; void show(int i, int j) // 回溯输出子序列 { if (i <= 0 || j <= 0) return; if (f[i][j] == 1) { show(i - 1, j - 1); printf("%c ", s1[i - 1]); } if (f[i][j] == 2) show(i, j - 1); if (f[i][j] == 3) show(i - 1, j); } void getLCS() { int n = strlen(s1), m = strlen(s2); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s1[i - 1] == s2[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; f[i][j] = 1; } else if (dp[i][j - 1] >= dp[i - 1][j]) { dp[i][j] = dp[i][j - 1]; f[i][j] = 2; } else { dp[i][j] = dp[i - 1][j]; f[i][j] = 3; } } } printf("%d\n", dp[n][m]); show(n, m); puts(""); } int main(void) { while (scanf("%s%s", s1, s2) != EOF) { memset(dp, 0, sizeof(dp)); memset(f, 0, sizeof(f)); getLCS(); } system("pause"); return 0; }
四,最长上升子序列
1,推导
设数组 a 长度为 n,求 a[] 的 LIS。
设 f[ i ] 代表:
以 a[ i ] 为最大值的最长上升子序列的长度。
因为是子序列,不要求连续,所以如果要求 f[ i ] 的值,
那么 a[ 0~i-1 ] 中任一取 a[ j ],满足 a[ j ] < a[ i ],
则 以 a[ j ] 为最大值的最长上升子序列 可以和 a[ j ] 组成一个新的上升序列,是以 a[ i ] 为最大值的。
原则上,当 j 离 i 越近,f[ j ] 就应该越大;当 a[ j ] 离 a[ i ] 越近,则 f[ j ] 就应该越大。如果我们能直接定位最大 f[ j ] 的值,则 f[ i ] 就等于 f[ j ] + 1
但显然,我们无法综合上述两种指标,所以只有将所有的 f[ j ] 都遍历一遍,取最大值即为 f[ i ]。
而如果找不到任何一个 a[ j ] 比 a[ i ] 小,说明该上升序列只有它本身,则 f[ i ] = 1。
所以有递推式:
if a[ j ] < a[ i ]
f [ i ] = max( f[ j ] + 1, f[ i ]),j 在 [0, i-1] 区间循环
递推初始条件:
f[ i ] = 1,0 <= i < n
2,例题
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1257
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #include<algorithm> using namespace std; #define N 100000+5 #define inf 0x3f3f3f3f int a[N], f[N]; void getLIS(int n) { int rs = -inf; for (int i = 0; i < n; i++) { f[i] = 1; for (int j = 0; j < i; j++) if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1); rs = max(rs, f[i]); } printf("%d\n", rs); } int main(void) { int n; while (scanf("%d", &n) != EOF) { memset(f, 0, sizeof(f)); for (int i = 0; i < n; i++) scanf("%d", &a[i]); getLIS(n); } system("pause"); return 0; }
=========== ========= ======== ======= ====== ===== ==== === == =
颂 无门和尚(宋)
春有百花秋有月,夏有凉风东有雪。
若无闲事挂心头,便是人间好时节。