概念
首先,要理解子串 和子序列 的含义:
子串 :来源于原序列连续的一段。
子序列 :来源于原序列中元素相对顺序不变的一段,不要求元素连续。
1. L C S \mathrm{LCS} LCS (最长公共子序列)
1.1 转移方程
给定两个序列 A A A 、B B B ,设 C [ i , j ] C[i, j] C [ i , j ] 为 L C S ( A i , B j ) \mathrm{LCS}(A_i, B_j) LCS ( A i , B j ) 的长度,其中 A i A_i A i 、B j B_j B j 分别表示 A A A 从首元素到第 i i i 个元素的一段、B B B 从首元素到第 j j j 个元素的一段, a i a_i a i 、b i b_i b i 分别表示 A A A 中第 i i i 个元素、B B B 中第 j j j 个元素,序列 A A A 和 B B B 的长度分别为 n n n 和 m m m 。则 L C S \mathrm{LCS} LCS 的状态转移方程为:
C [ i , j ] = 0 ( i = 0 ∨ j = 0 ) C[i, j] = 0 \quad (i = 0 \vee j = 0 ) C [ i , j ] = 0 ( i = 0 ∨ j = 0 )
C [ i , j ] = C [ i − 1 , j − 1 ] + 1 ( i , j > 0 ∧ a i = b j ) C[i, j] = C[i-1, j-1] + 1 \quad (i,j > 0 \wedge a_i = b_j) C [ i , j ] = C [ i − 1 , j − 1 ] + 1 ( i , j > 0 ∧ a i = b j )
C [ i , j ] = m a x ( C [ i − 1 , j ] , C [ i , j − 1 ] ) ( i , j > 0 ∧ a i ≠ b j ) C[i, j] = \mathrm{max}(C[i-1, j], C[i, j-1]) \quad (i,j > 0 \wedge a_i \neq b_j) C [ i , j ] = max ( C [ i − 1 , j ] , C [ i , j − 1 ]) ( i , j > 0 ∧ a i = b j )
若还要求 L C S \mathrm{LCS} LCS 的个数,则可以设 D [ i , j ] D[i, j] D [ i , j ] 为 L C S ( A i , B j ) \mathrm{LCS}(A_i, B_j) LCS ( A i , B j ) 的个数,在上述转移方程求完 C [ i , j ] C[i, j] C [ i , j ] 后,进一步求出 D [ i , j ] D[i, j] D [ i , j ] :
初始化:D [ i , j ] = 1 ( i = 0 ∨ j = 0 ) D[i, j] = 1 \quad (i = 0 \vee j = 0) D [ i , j ] = 1 ( i = 0 ∨ j = 0 )
根据可转移到 D [ i , j ] D[i, j] D [ i , j ] 的三个方向依次累加个数:
D [ i , j ] = 0 D [ i , j ] = D [ i , j ] + D [ i − 1 , j − 1 ] ( a i = b j ) D [ i , j ] = D [ i , j ] + D [ i − 1 , j ] ( C [ i , j ] = C [ i − 1 , j ] ) D [ i , j ] = D [ i , j ] + D [ i , j − 1 ] ( C [ i , j ] = C [ i , j − 1 ] ) D [ i , j ] = D [ i , j ] − D [ i − 1 , j − 1 ] ( C [ i , j ] = C [ i − 1 , j − 1 ] ) \begin{array}{l}
D[i, j] = 0 \\
D[i, j] = D[i, j] + D[i-1, j-1] \quad (a_i = b_j) \\
D[i, j] = D[i, j] + D[i-1, j] \quad (C[i, j] = C[i-1, j]) \\
D[i, j] = D[i, j] + D[i, j-1] \quad (C[i, j] = C[i, j-1]) \\
D[i, j] = D[i, j] - D[i-1, j-1] \quad (C[i, j] = C[i-1, j-1])
\end{array}
D [ i , j ] = 0 D [ i , j ] = D [ i , j ] + D [ i − 1 , j − 1 ] ( a i = b j ) D [ i , j ] = D [ i , j ] + D [ i − 1 , j ] ( C [ i , j ] = C [ i − 1 , j ]) D [ i , j ] = D [ i , j ] + D [ i , j − 1 ] ( C [ i , j ] = C [ i , j − 1 ]) D [ i , j ] = D [ i , j ] − D [ i − 1 , j − 1 ] ( C [ i , j ] = C [ i − 1 , j − 1 ])
若还要求出 L C S \mathrm{LCS} LCS ,则可以根据 C [ i , j ] C[i, j] C [ i , j ] 与 C [ i − 1 , j − 1 ] C[i-1, j-1] C [ i − 1 , j − 1 ] 、C [ i − 1 , j ] C[i-1, j] C [ i − 1 , j ] 和 C [ i , j − 1 ] C[i, j-1] C [ i , j − 1 ] 的大小关系来进行回溯,或者直接用数组记录各个长度的相等点(相等点指 ( i , j ) (i,j) ( i , j ) ,其满足 A i = B j A_i = B_j A i = B j )。
1.2 分析
关于第一个转移方程:此转移方程比较好理解,最终结果为 C [ n , m ] C[n, m] C [ n , m ] ,该算法时间和空间复杂度为 O ( n m ) O(nm) O ( nm ) 。
关于第二个转移方程:我们发现求 C [ i , j ] C[i, j] C [ i , j ] 时,其结果仅依赖三个方向的值,即 C [ i − 1 , j − 1 ] , C [ i − 1 , j ] , C [ i , j − 1 ] C[i-1, j-1], C[i-1, j], C[i, j-1] C [ i − 1 , j − 1 ] , C [ i − 1 , j ] , C [ i , j − 1 ] 。也就是说,L C S ( A i , B j ) LCS(A_i, B_j) L CS ( A i , B j ) 的结果来源于这三个方向,求 C [ i , j ] C[i, j] C [ i , j ] 的过程只选取了最大长度,而没有考虑方案数。因此,我们可以用数组 D [ i , j ] D[i, j] D [ i , j ] 来保存 L C S ( A i , B j ) LCS(A_i, B_j) L CS ( A i , B j ) 的方案数。
当 a i = b j a_i = b_j a i = b j 时,说明 L C S ( A i , B j ) LCS(A_i, B_j) L CS ( A i , B j ) 可以来自于 L C S ( A i − 1 , B j − 1 ) LCS(A_{i-1}, B_{j-1}) L CS ( A i − 1 , B j − 1 ) ,因此 D [ i , j ] = D [ i , j ] + D [ i − 1 , j − 1 ] D[i, j] = D[i, j] + D[i-1, j-1] D [ i , j ] = D [ i , j ] + D [ i − 1 , j − 1 ] 。
当 C [ i , j ] = C [ i − 1 , j ] C[i, j] = C[i-1, j] C [ i , j ] = C [ i − 1 , j ] 时,说明 L C S ( A i , B j ) LCS(A_i, B_j) L CS ( A i , B j ) 还可以来自于 L C S ( A i − 1 , B j ) LCS(A_{i-1}, B_j) L CS ( A i − 1 , B j ) ,因此 D [ i , j ] = D [ i , j ] + D [ i − 1 , j ] D[i, j] = D[i, j] + D[i-1, j] D [ i , j ] = D [ i , j ] + D [ i − 1 , j ] 。
当 C [ i , j ] = C [ i , j − 1 ] C[i, j] = C[i, j-1] C [ i , j ] = C [ i , j − 1 ] 时,说明 L C S ( A i , B j ) LCS(A_i, B_j) L CS ( A i , B j ) 还可以来自于 L C S ( A i , B j − 1 ) LCS(A_i, B_{j-1}) L CS ( A i , B j − 1 ) ,因此 D [ i , j ] = D [ i , j ] + D [ i , j − 1 ] D[i, j] = D[i, j] + D[i, j-1] D [ i , j ] = D [ i , j ] + D [ i , j − 1 ] 。
但是我们会有一个疑问,即这三个方向是否存在重复方案数呢,即某个方向是来自于另一个方向的。此即当 C [ i , j ] = C [ i − 1 , j − 1 ] C[i, j] = C[i-1, j-1] C [ i , j ] = C [ i − 1 , j − 1 ] 时,说明 a i ≠ b j a_i \neq b_j a i = b j ,此时 D [ i − 1 , j ] D[i-1, j] D [ i − 1 , j ] 和 D [ i , j − 1 ] D[i, j-1] D [ i , j − 1 ] 都重复统计了 D [ i − 1 , j − 1 ] D[i-1, j-1] D [ i − 1 , j − 1 ] ,根据容斥原理,需要减去一个,即 D [ i , j ] = D [ i , j ] − D [ i − 1 , j − 1 ] D[i, j] = D[i, j] - D[i-1, j-1] D [ i , j ] = D [ i , j ] − D [ i − 1 , j − 1 ] 。
对于上述还需要说明的是:
当 a i = b j a_i = b_j a i = b j 时,易知 C [ i − 1 , j ] , C [ i , j − 1 ] ≤ C [ i − 1 , j − 1 ] + 1 C[i-1, j], C[i, j-1] \leq C[i-1, j-1] + 1 C [ i − 1 , j ] , C [ i , j − 1 ] ≤ C [ i − 1 , j − 1 ] + 1 。且当 C [ i − 1 , j ] = C [ i − 1 , j − 1 ] + 1 C[i-1, j] = C[i-1, j-1] + 1 C [ i − 1 , j ] = C [ i − 1 , j − 1 ] + 1 时,根据上述的法则可知,其方案数 D [ i − 1 , j ] D[i-1, j] D [ i − 1 , j ] 必定不来自于 D [ i − 1 , j − 1 ] D[i-1, j-1] D [ i − 1 , j − 1 ] ;C [ i , j − 1 ] C[i, j-1] C [ i , j − 1 ] 同理,因此此时可以累加三者。
当 a i ≠ b j a_i \neq b_j a i = b j 时,由 C [ i , j ] C[i,j] C [ i , j ] 的计算可知,此时 D [ i , j ] D[i, j] D [ i , j ] 只来源于 D [ i − 1 , j ] D[i-1, j] D [ i − 1 , j ] 和 D [ i , j − 1 ] D[i, j-1] D [ i , j − 1 ] 。且当 C [ i − 1 , j − 1 ] < C [ i , j ] C[i-1, j-1] \lt C[i, j] C [ i − 1 , j − 1 ] < C [ i , j ] 时,D [ i − 1 , j − 1 ] D[i-1, j-1] D [ i − 1 , j − 1 ] 不对 D [ i , j ] D[i, j] D [ i , j ] 造成贡献,此时 D [ i − 1 , j ] D[i-1, j] D [ i − 1 , j ] 和 D [ i , j − 1 ] D[i, j-1] D [ i , j − 1 ] 没有交叉,可直接累加;而当 C [ i − 1 , j − 1 ] = C [ i , j ] C[i-1, j-1] = C[i, j] C [ i − 1 , j − 1 ] = C [ i , j ] 时,根据上述法则,D [ i , j ] D[i, j] D [ i , j ] 会同时贡献 D [ i − 1 , j ] D[i-1, j] D [ i − 1 , j ] 和 D [ i , j − 1 ] D[i, j-1] D [ i , j − 1 ] ,因此 D [ i − 1 , j ] D[i-1, j] D [ i − 1 , j ] 和 D [ i , j − 1 ] D[i, j-1] D [ i , j − 1 ] 交叉了 D [ i − 1 , j − 1 ] D[i-1, j-1] D [ i − 1 , j − 1 ] ,需要去除一份 D [ i − 1 , j − 1 ] D[i-1, j-1] D [ i − 1 , j − 1 ] 。
最终结果即为 D [ n , m ] D[n, m] D [ n , m ] ,综合的时间和空间复杂度仍为 O ( n m ) O(nm) O ( nm ) 。
【注】此类二维动态规划的空间复杂度可以用滚动数组降为 O ( max ( n , m ) ) O(\max(n, m)) O ( max ( n , m )) 。
1.3 扩展
相较于用动态规划思想求 L C S \mathrm{LCS} LCS 长度的复杂度为 O ( n m ) O(nm) O ( nm ) ,求 L I S \mathrm{LIS} LIS 长度的最优复杂度可以达到 O ( n log n ) O(n \log{n}) O ( n log n ) (见下文)。幸运的是部分 L C S \mathrm{LCS} LCS 问题可以用 L I S \mathrm{LIS} LIS 来解。
若 L C S \mathrm{LCS} LCS 的两个序列中的其中一个满足元素互异条件,则可以用 L I S \mathrm{LIS} LIS 来解。
在此类 L C S \mathrm{LCS} LCS 中,不防假设序列 A A A 中元素是互异的,设序列 A A A 的长度为 n n n ,则我们可以考虑将 A A A 的元素按照出现顺序依次映射到 1 … n 1 \ldots n 1 … n 上,从而得到 A A A 中元素与数值的一一对应关系。然后根据 A A A 元素的映射表,来计算序列 B B B 元素对应的映射值;对于在 B B B 中存在而不在 A A A 中存在的元素,直接舍弃即可,因为它们必然不会出现在 L C S \mathrm{LCS} LCS 中。
如此,映射完 B B B 得到的数值序列设为 C C C ,其长度为 m m m 。则此时只需要计算序列 C C C 的 L I S \mathrm{LIS} LIS 即可。这是因为序列 A A A 映射后的序列是一个递增的数值序列,因此 A A A 和 C C C 的公共子序列也是递增子序列。由于 C C C 中所有数值都在 A A A 的映射表中,故 C C C 的最长递增子序列也是二者的最长公共子序列,因此只需要求序列 C C C 的最长递增子序列即可。
1.4 代码
仅求 L C S \mathrm{LCS} LCS 长度,时间复杂度为 O ( n m ) O(nm) O ( nm ) ,空间复杂度为 O ( max ( n , m ) ) O(\max(n, m)) O ( max ( n , m )) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> using namespace std;#ifndef _LCS_ #define _LCS_ #define ll int #define ele char #define MAXN 100005 struct LCS { ll n, m; ele A[MAXN], B[MAXN]; LCS (): n (0 ), m (0 ) {} ll length () { ll uplen[MAXN] = {0 }, downlen[MAXN] = {0 }; ll *puplen = uplen, *pdownlen = downlen; for (ll i = 1 ; i <= n; ++i) { for (ll j = 1 ; j <= m; ++j) { if (A[i-1 ] == B[j-1 ]) { pdownlen[j] = puplen[j-1 ] + 1 ; } else { pdownlen[j] = max (puplen[j], pdownlen[j-1 ]); } } swap (puplen, pdownlen); } return puplen[m]; } }; #endif
若 L C S \mathrm{LCS} LCS 两个序列中至少一个(假设为 A A A )满足元素互异条件,则求此类 L C S \mathrm{LCS} LCS 长度的时间复杂度为 O ( n + m log m ) O(n + m \log{m}) O ( n + m log m ) ,空间复杂度为 O ( max ( n , m ) ) O(\max(n, m)) O ( max ( n , m )) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std;#ifndef _LCS_ #define _LCS_ #define ll int #define ele char #define MAXN 100005 struct LCS { ll n, m; ele A[MAXN], B[MAXN]; LCS (): n (0 ) {} ll length () { unordered_map <ele,ll> mp; for (ll i = 0 ; i < n; ++i) { mp[A[i]] = i; } ll cnt = 0 ; ll len[m]; for (ll i = 0 ; i < m; ++i) { if (mp.count (B[i]) > 0 ) { len[cnt++] = mp[B[i]]; } } vector <ll> lis; lis.push_back (len[0 ]); for (ll i = 1 ; i < cnt; ++i) { if (len[i] > lis.back ()) { lis.push_back (len[i]); } else { ll pos = lower_bound (lis.begin (), lis.end (), len[i]) - lis.begin (); lis[pos] = min (lis[pos], len[i]); } } return lis.size (); } }; #endif
既求 L C S \mathrm{LCS} LCS 长度,又求 L C S \mathrm{LCS} LCS 的个数,时间复杂度为 O ( n m ) O(nm) O ( nm ) ,空间复杂度为 O ( max ( n , m ) ) O(\max(n, m)) O ( max ( n , m )) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <bits/stdc++.h> using namespace std;#ifndef _LCS_ #define _LCS_ #define ll int #define ele char #define MAXN 10005 struct LCS { ll n, m; ele A[MAXN], B[MAXN]; ll mxlen, mxcnt; LCS (): n (0 ), m (0 ), mxlen (0 ), mxcnt (0 ) {} void dp () { ll uplen[MAXN] = {0 }, downlen[MAXN] = {0 }; ll *puplen = uplen, *pdownlen = downlen; ll upcnt[MAXN] = {0 }, downcnt[MAXN] = {0 }; ll *pupcnt = upcnt, *pdowncnt = downcnt; for (ll i = 0 ; i <= m; ++i) { pupcnt[i] = 1 ; } pdowncnt[0 ] = 1 ; for (ll i = 1 ; i <= n; ++i) { for (ll j = 1 ; j <= m; ++j) { pdowncnt[j] = 0 ; pdownlen[j] = max (puplen[j], pdownlen[j-1 ]); if (A[i-1 ] == B[j-1 ]) { pdownlen[j] = puplen[j-1 ] + 1 ; pdowncnt[j] = pupcnt[j-1 ]; } if (pdownlen[j] == puplen[j]) { pdowncnt[j] = pdowncnt[j] + pupcnt[j]; } if (pdownlen[j] == pdownlen[j-1 ]) { pdowncnt[j] = pdowncnt[j] + pdowncnt[j-1 ]; } if (pdownlen[j] == puplen[j-1 ]) { pdowncnt[j] = pdowncnt[j] - pupcnt[j-1 ]; } } swap (puplen, pdownlen); swap (pupcnt, pdowncnt); } mxlen = puplen[m]; mxcnt = pupcnt[m]; } }; #endif
既求 L C S \mathrm{LCS} LCS 长度,又求一个 L C S \mathrm{LCS} LCS ,时/空间复杂度为 O ( n m ) O(nm) O ( nm ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> using namespace std;#ifndef _LCS_ #define _LCS_ #define ll int #define ele char #define MAXN 1005 struct LCS { ll n, m; ele A[MAXN], B[MAXN]; ll mxlen; ll len[MAXN][MAXN]; stack <ele> path; LCS (): n (0 ), m (0 ), mxlen (0 ) { memset (len, 0 , sizeof (len)); } void dp () { for (ll i = 1 ; i <= n; ++i) { for (ll j = 1 ; j <= m; ++j) { if (A[i-1 ] == B[j-1 ]) { len[i][j] = len[i-1 ][j-1 ] + 1 ; } else { len[i][j] = max (len[i-1 ][j], len[i][j-1 ]); } } } mxlen = len[n][m]; } void getlcs () { if (mxlen == 0 ) { dp (); } ll lcslen = mxlen; ll i = n-1 , j = m-1 ; while (lcslen > 0 ) { if (A[i] == B[j]) { path.push (A[i]); --i, --j; --lcslen; } else if (len[i+1 ][j+1 ] == len[i][j+1 ]) { --i; } else { --j; } } } }; #endif
既求 L C S \mathrm{LCS} LCS 长度和个数,又求所有的 L C S \mathrm{LCS} LCS (包括相同的),时/空间复杂度都是指数的。
【注】若要求所有不同的 L C S \mathrm{LCS} LCS ,只需用 unordered_map 来记录 L C S \mathrm{LCS} LCS 是否已经打印过,打印时判定哈希值是否存在即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 #include <bits/stdc++.h> using namespace std;#ifndef _LCS_ #define _LCS_ #define ll int #define ele char #define MAXN 1005 #define MAXM 10005 struct LCS { struct Edge { ll len; ll next, to; ele element; }; struct MAPPING { ll apos, bpos; ll count; MAPPING (ll _apos, ll _bpos, ll _count): apos (_apos), bpos (_bpos), count (_count) {} bool operator < (const MAPPING mp) const { return apos < mp.apos && bpos < mp.bpos; } }; ll n, m; ele A[MAXN], B[MAXN]; ll mxlen, mxcnt; ll len[MAXN][MAXN]; vector <MAPPING> lcs[MAXN]; ll vcnt, ecnt; ll head[MAXM]; ll vertex[MAXN][MAXN]; Edge graph[MAXM]; void addedge (ll u, ll v, ll len, ele e) { graph[ecnt].element = e; graph[ecnt].len = len; graph[ecnt].to = v; graph[ecnt].next = head[u]; head[u] = ecnt++; } LCS (): n (0 ), m (0 ), mxlen (0 ), mxcnt (0 ) { vcnt = 1 , ecnt = 0 ; memset (len, 0 , sizeof (len)); memset (head, -1 , sizeof (head)); memset (vertex, 0 , sizeof (vertex)); } void dp () { for (ll i = 1 ; i <= n; ++i) { for (ll j = 1 ; j <= m; ++j) { if (A[i-1 ] == B[j-1 ]) { len[i][j] = len[i-1 ][j-1 ] + 1 ; lcs[len[i][j]].push_back (MAPPING (i-1 , j-1 , 0 )); } else { len[i][j] = max (len[i-1 ][j], len[i][j-1 ]); } } } mxlen = len[n][m]; } void getlcss () { if (mxlen == 0 ) { dp (); } for (ll i = 0 ; i < lcs[mxlen].size (); ++i) { lcs[mxlen][i].count = 1 ; vertex[lcs[mxlen][i].apos][lcs[mxlen][i].bpos] = vcnt++; } for (ll i = mxlen - 1 ; i > 0 ; --i) { for (ll j = 0 ; j < lcs[i+1 ].size (); ++j) { if (lcs[i+1 ][j].count > 0 ) { ll napos = lcs[i+1 ][j].apos, nbpos = lcs[i+1 ][j].bpos; for (ll k = 0 ; k < lcs[i].size (); ++k) { if (lcs[i][k] < lcs[i+1 ][j]) { lcs[i][k].count = lcs[i][k].count + lcs[i+1 ][j].count; ll apos = lcs[i][k].apos, bpos = lcs[i][k].bpos; if (!vertex[apos][bpos]) { vertex[apos][bpos] = vcnt++; } addedge (vertex[apos][bpos], vertex[napos][nbpos], i+1 , A[napos]); } } } } } mxcnt = 0 ; for (ll j = 0 ; j < lcs[1 ].size (); ++j) { if (lcs[1 ][j].count > 0 ) { mxcnt += lcs[1 ][j].count; ll apos = lcs[1 ][j].apos, bpos = lcs[1 ][j].bpos; addedge (0 , vertex[apos][bpos], 1 , A[apos]); } } } void printlcss () { if (mxcnt == 0 ) { getlcss (); } stack <ll> clss; for (ll e = head[0 ]; e != -1 ; e = graph[e].next) { clss.push (e); } vector <ele> print; while (!clss.empty ()) { ll e = clss.top (); clss.pop (); ll u = graph[e].to; print.resize (graph[e].len - 1 ); print.push_back (graph[e].element); if (head[u] == -1 ) { for (ll i = 0 ; i < print.size (); ++i) { printf ("%c" , print[i]); } printf ("\n" ); } else { for (ll e = head[u]; e != -1 ; e = graph[e].next) { clss.push (e); } } } } }; #endif
2. L I S \mathrm{LIS} LIS (最长递增子序列)
2.1 状态转移方程
这里考虑严格递增(不严格递增类似)。给定一个序列 A A A ,设 a i a_i a i 表示 A A A 中的第 i i i 个元素,I [ i ] I[i] I [ i ] 为以 a i a_i a i 结尾最长递增子序列的长度,假定 a 0 < a i , ∀ i > 0 a_0 \lt a_i, \forall i \gt 0 a 0 < a i , ∀ i > 0 ,则其状态转移方程为:
I [ i ] = 0 ( i = 0 ) I[i] = 0 \quad (i = 0) I [ i ] = 0 ( i = 0 )
I [ i ] = m a x ( I [ j ] + 1 ) ( 0 ≤ j < i ∧ a i > a j ) I[i] = \mathrm{max}(I[j] + 1) \quad (0 \leq j \lt i \wedge a_i \gt a_j) I [ i ] = max ( I [ j ] + 1 ) ( 0 ≤ j < i ∧ a i > a j )
2.2 分析
若只是要求求出 L I S \mathrm{LIS} LIS 的长度,则可用一个栈来储存 L I S \mathrm{LIS} LIS ,并结合二分来维护 L I S \mathrm{LIS} LIS ,该栈的最后一个元素为最长 L I S \mathrm{LIS} LIS 的尾元素,栈长即为 L I S \mathrm{LIS} LIS ,算法复杂度为 O ( n log n ) O(n \log{n}) O ( n log n ) 。
若要求输出 L I S \mathrm{LIS} LIS ,则可以考虑求 A A A 和 s o r t ( A ) \mathrm{sort}(A) sort ( A ) 的 L C S \mathrm{LCS} LCS ,此 L C S \mathrm{LCS} LCS 即为 A A A 的最长不减子序列,进一步得到 L I S \mathrm{LIS} LIS 只需剔除那些多余相等的元素,算法复杂度为 O ( n 2 ) O(n^2) O ( n 2 ) 。
2.3 代码
仅求 L I S \mathrm{LIS} LIS 的长度,时间复杂度为 O ( n 2 ) O(n^2) O ( n 2 ) ,空间复杂度为 O ( n ) O(n) O ( n ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> using namespace std;#ifndef _LIS_ #define _LIS_ #define ll int #define ele char #define MAXN 100005 struct LIS { ll n; ele A[MAXN]; LIS (): n (0 ) {} ll length () { ll rst = 0 ; ll lislen[n] = {0 }; for (ll i = 1 ; i <= n; ++i) { lislen[i] = 1 ; for (ll j = 1 ; j <= i; ++j) { if (A[j-1 ] < A[i-1 ]) { lislen[i] = max (lislen[i], lislen[j]+1 ); } } rst = max (rst, lislen[i]); } return rst; } }; #endif
仅求 L I S \mathrm{LIS} LIS 的长度,时间复杂度为 O ( n log n ) O(n \log{n}) O ( n log n ) ,空间复杂度为 O ( n ) O(n) O ( n ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> using namespace std;#ifndef _LIS_ #define _LIS_ #define ll int #define ele char #define MAXN 100005 struct LIS { ll n; ele A[MAXN]; LIS (): n (0 ) {} ll length () { vector <ll> lis; lis.push_back (A[0 ]); for (ll i = 1 ; i < n; ++i) { if (A[i] > lis.back ()) { lis.push_back (A[i]); } else { vector <ll>::iterator it = lower_bound (lis.begin (), lis.end (), A[i]); *it = A[i]; } } return lis.size (); } }; #endif
若要求输出 L I S \mathrm{LIS} LIS ,则参考求 L C S ( A , s o r t ( A ) ) \mathrm{LCS}(A, \mathrm{sort}(A)) LCS ( A , sort ( A )) ,代码参考 L C S \mathrm{LCS} LCS 部分。
3. L C I S \mathrm{LCIS} LCIS (最长递增公共子序列)
3.1 状态转移方程
此问题可以看成是 L I S \mathrm{LIS} LIS 问题和 L C S \mathrm{LCS} LCS 问题的重叠。给定两个序列 A A A 、B B B ,设序列 A A A 和 B B B 的长度分别为 n n n 和 m m m ,C I [ i ] [ j ] CI[i][j] C I [ i ] [ j ] 为 A A A 中前 i i i 个元素,B B B 中前 j j j 个元素且以 B [ j ] B[j] B [ j ] 结尾的 L C I S \mathrm{LCIS} LCIS ,则其状态转移方程为:
C I [ i ] [ j ] = 0 ( i = 0 ∨ j = 0 ) CI[i][j] = 0 \quad (i = 0 \vee j = 0) C I [ i ] [ j ] = 0 ( i = 0 ∨ j = 0 )
C I [ i ] [ j ] = C I [ i − 1 ] [ j ] ( A [ i ] ≠ B [ j ] ) CI[i][j] = CI[i-1][j] \quad (A[i] \neq B[j]) C I [ i ] [ j ] = C I [ i − 1 ] [ j ] ( A [ i ] = B [ j ])
C I [ i ] [ j ] = m a x 1 ≤ k ≤ j − 1 ∧ B [ k ] < A [ i ] ( C I [ i − 1 ] [ k ] ) + 1 ( A [ i ] = B [ j ] ) CI[i][j] = \mathrm{max}_{1 \leq k \leq j - 1 \wedge B[k] \lt A[i]}(CI[i-1][k]) + 1 \quad (A[i] = B[j]) C I [ i ] [ j ] = max 1 ≤ k ≤ j − 1 ∧ B [ k ] < A [ i ] ( C I [ i − 1 ] [ k ]) + 1 ( A [ i ] = B [ j ])
由此状态转移方程,可以写出最小时间复杂度为 O ( n m ) O(nm) O ( nm ) 的算法。同样用二维数组(或一维滚动数组,参考附录中「有关空间复杂度的降低」)。
3.2 分析
如何理解上述状态转移方程:
对于第一个等式,如果 A [ i ] ≠ B [ j ] A[i] \neq B[j] A [ i ] = B [ j ] ,而 C I [ i ] [ j ] CI[i][j] C I [ i ] [ j ] 是以 B [ j ] B[j] B [ j ] 为结尾的 L C I S \mathrm{LCIS} LCIS 长度,则必有 C I [ i ] [ j ] = C I [ i − 1 ] [ j ] CI[i][j] = CI[i-1][j] C I [ i ] [ j ] = C I [ i − 1 ] [ j ] 。
对于第二个等式,如果 A [ i ] = B [ j ] A[i] = B[j] A [ i ] = B [ j ] ,则 C I [ i ] [ j ] CI[i][j] C I [ i ] [ j ] 应该为 C I [ i − 1 ] [ 1 ] ∼ C I [ i − 1 ] [ j − 1 ] CI[i-1][1] \sim CI[i-1][j-1] C I [ i − 1 ] [ 1 ] ∼ C I [ i − 1 ] [ j − 1 ] 中满足 B [ k ] < A [ i ] B[k] \lt A[i] B [ k ] < A [ i ] (满足递增条件)的最大值 + 1 +1 + 1 。在寻找 m a x ( C I [ i − 1 ] [ k ] ) \mathrm{max}(CI[i-1][k]) max ( C I [ i − 1 ] [ k ]) 的时候,其实可以在求 C I [ i − 1 ] [ j − 1 ] CI[i-1][j-1] C I [ i − 1 ] [ j − 1 ] 时求出,即每次计算 C I [ i ] [ j ] CI[i][j] C I [ i ] [ j ] 的时候,同时计算出 m a x ( C I [ i ] [ k ] ) \mathrm{max}(CI[i][k]) max ( C I [ i ] [ k ]) 。这样可以将时间复杂度降为 O ( n m ) O(nm) O ( nm ) 。具体实现步骤如下:
初始化 m a x = 0 \mathrm{max} = 0 max = 0 ,内层循环 j = 1 : m j = 1 : m j = 1 : m 。
如果 A [ i ] > B [ j ] ∧ m a x < C I [ i − 1 ] [ j ] A[i] \gt B[j] \wedge \mathrm{max} \lt CI[i-1][j] A [ i ] > B [ j ] ∧ max < C I [ i − 1 ] [ j ] ,则 m a x = C I [ i − 1 ] [ j ] \mathrm{max} = CI[i-1][j] max = C I [ i − 1 ] [ j ] 。
这样,当循环到 A [ i ] = B [ j ] A[i] = B[j] A [ i ] = B [ j ] 时,m a x \mathrm{max} max 即为 m a x ( C I [ i − 1 ] [ k ] ) \mathrm{max}(CI[i-1][k]) max ( C I [ i − 1 ] [ k ]) 。为什么是 A [ i ] > B [ j ] A[i] > B[j] A [ i ] > B [ j ] 呢,因为这个时候 A [ i ] A[i] A [ i ] 为考虑中的公共元素,当它大于 B [ j ] B[j] B [ j ] 时,说明 B [ j ] B[j] B [ j ] 后面可能有和它相等的元素,故 A [ i ] A[i] A [ i ] 可能接到 B [ j ] B[j] B [ j ] 的后面。若是 A [ i ] < B [ j ] A[i] \lt B[j] A [ i ] < B [ j ] ,则 A [ i ] A[i] A [ i ] 不可能接到 B [ j ] B[j] B [ j ] 的后面(因为递增子序列)。
这个问题如果允许复杂度再大一点话,其实是可以转化为三个序列求 L C S \mathrm{LCS} LCS 的问题,这三个序列分别是 A A A ,B B B ,s o r t ( A ) \mathrm{sort}(A) sort ( A ) (或 s o r t ( B ) \mathrm{sort}(B) sort ( B ) )。若 A A A 中有重复元素,则需剔除 s o r t ( A ) \mathrm{sort(A)} sort ( A ) 中的重复元素(不防记为 C C C ),再求三者的 L C S ( A , B , C ) \mathrm{LCS}(A,B,C) LCS ( A , B , C ) (利用三维数组实现,若用滚动数组实现则可以降为二维,参考附录中「有关空间复杂度的降低」)。可以利用反证法 给出具体证明:
显然是 A , B A,B A , B 的递增公共子序列,倘若其不是 L C I S ( A , B ) \mathrm{LCIS}(A,B) LCIS ( A , B ) :
要么是 ∣ L C I S ( A , B ) ∣ > ∣ L C S ( A , B , C ) ∣ |\mathrm{LCIS}(A,B)| \gt |\mathrm{LCS}(A,B,C)| ∣ LCIS ( A , B ) ∣ > ∣ LCS ( A , B , C ) ∣ ,这显然不可能,因为 L C I S ( A , B ) \mathrm{LCIS}(A,B) LCIS ( A , B ) 是 A , B A,B A , B 的递增子序列,所以也是 C C C 的子序列,因此 L C I S ( A , B ) \mathrm{LCIS}(A,B) LCIS ( A , B ) 是 A , B , C A,B,C A , B , C 的公共子序列,因此 ∣ L C I S ( A , B ) ∣ ≤ ∣ L C S ( A , B , C ) ∣ |\mathrm{LCIS}(A,B)| \leq |\mathrm{LCS}(A,B,C)| ∣ LCIS ( A , B ) ∣ ≤ ∣ LCS ( A , B , C ) ∣ ,即命题矛盾。
要么是 ∣ L C I S ( A , B ) ∣ < ∣ L C S ( A , B , C ) ∣ |\mathrm{LCIS}(A,B)| \lt |\mathrm{LCS}(A,B,C)| ∣ LCIS ( A , B ) ∣ < ∣ LCS ( A , B , C ) ∣ ,由于 L C S ( A , B , C ) \mathrm{LCS}(A,B,C) LCS ( A , B , C ) 是 A , B A,B A , B 的递增子序列,因此 ∣ L C I S ( A , B ) ∣ ≥ ∣ L C S ( A , B , C ) ∣ |\mathrm{LCIS}(A,B)| \geq |\mathrm{LCS}(A,B,C)| ∣ LCIS ( A , B ) ∣ ≥ ∣ LCS ( A , B , C ) ∣ ,即命题矛盾。
综上,L C I S ( A , B ) = L C S ( A , B , C ) \mathrm{LCIS}(A,B) = \mathrm{LCS}(A,B,C) LCIS ( A , B ) = LCS ( A , B , C ) 。
3.3 代码
既求 L C I S \mathrm{LCIS} LCIS 长度,又求 L C I S \mathrm{LCIS} LCIS 个数,时间复杂度 O ( n m ) O(nm)
O ( nm ) ,空间复杂度 O ( m a x ( n , m ) ) O(\mathrm{max}(n, m)) O ( max ( n , m )) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <bits/stdc++.h> using namespace std;#ifndef _LCIS_ #define _LCIS_ #define ll int #define ele int #define MAXN 10005 struct LCIS { ll n, m; ele A[MAXN], B[MAXN]; ll mxlen, mxcnt; LCIS (): n (0 ), m (0 ), mxlen (0 ), mxcnt (0 ) {} void dp () { ll uplen[MAXN] = {0 }, downlen[MAXN] = {0 }; ll *puplen = uplen, *pdownlen = downlen; ll upcnt[MAXN] = {0 }, downcnt[MAXN] = {0 }; ll *pupcnt = upcnt, *pdowncnt = downcnt; for (ll i = 1 ; i <= n; ++i) { ll k = 0 ; ll lencnt[MAXN] = {0 }; lencnt[0 ] = 1 ; for (ll j = 1 ; j <= m; ++j) { if (A[i-1 ] == B[j-1 ]) { pdownlen[j] = puplen[k] + 1 ; pdowncnt[j] = lencnt[puplen[k]]; } else { pdownlen[j] = puplen[j]; pdowncnt[j] = pupcnt[j]; if (A[i-1 ] > B[j-1 ]) { if (puplen[j] >= puplen[k]) { k = j; lencnt[puplen[j]] += pupcnt[j]; } } } } swap (pdownlen, puplen); swap (pdowncnt, pupcnt); } mxlen = 0 ; for (ll j = 1 ; j <= m; ++j) { mxlen = max (mxlen, puplen[j]); } mxcnt = 0 ; for (ll j = 1 ; j <= m; ++j) { if (puplen[j] == mxlen) { mxcnt += pupcnt[j]; } } } }; #endif
既求 L C I S \mathrm{LCIS} LCIS 长度,又要求输出一个 L C I S \mathrm{LCIS} LCIS ,时空间复杂度为 O ( n m ) O(nm) O ( nm ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <bits/stdc++.h> using namespace std;#ifndef _LCIS_ #define _LCIS_ #define ll int #define ele int #define MAXN 555 #define MAXELE 0x7fffffff struct LCIS { ll n, m; ele A[MAXN], B[MAXN]; ll len[MAXN][MAXN]; ll mxlen; stack <ele> path; LCIS (): n (0 ), m (0 ), mxlen (0 ) { memset (len, 0 , sizeof (len)); } void dp () { for (ll i = 1 ; i <= n; ++i) { ll k = 0 ; for (ll j = 1 ; j <= m; ++j) { if (A[i-1 ] == B[j-1 ]) { len[i][j] = len[i-1 ][k] + 1 ; } else { len[i][j] = len[i-1 ][j]; if (A[i-1 ] > B[j-1 ]) { if (len[i-1 ][j] > len[i-1 ][k]) { k = j; } } } } } mxlen = 0 ; for (ll j = 1 ; j <= m; ++j) { mxlen = max (mxlen, len[n][j]); } } void getlcis () { if (mxlen == 0 ) { dp (); } ll i = n, j = m; ll curlen = mxlen; ele mxele = MAXELE; while (curlen > 0 ) { if (len[i][j] != curlen || B[j-1 ] >= mxele) { --j; } else { if (A[i-1 ] != B[j-1 ]) { --i; } else { path.push (B[j-1 ]); mxele = B[j-1 ]; --curlen; --i, --j; } } } } }; #endif
附录
有关空间复杂度的降低
对于上面三种问题,若采用了数组来实现状态转移,且是逐行(或逐列)扫描,则可降低一维,因为上面三种问题的状态转移数组的每一位的求解时均只是利用到该位的左上及其正上和正左的元素,而且最终的答案在最后的那一行(或列)中,故可以减去一维,实现逐行(或逐列)重复扫描,从而降低了空间复杂度,这种降维方法也称为滚动数组 。