# LIS&LCS # LIS $LIS$全名$Longest\ Increasing\ Subsequence$ 也就是最常遞增子序列 顧名思義,最長遞增的子序列 在一個的序列中,找到一個子序列,使得這個子序列元素為遞增,且這個子序列的長度為最大。 ## DP解法 狀態 $dp[i]為a[i]$以為結尾的最長遞增子序列的長度(a為數列) 轉移式 $dp[i] = max(dp[i], max(dp[j]) + 1) ,if\ j\ <\ i\ and\ a[j]\ <\ a[i]$ 時間複雜度$O(n^2)$ 例子 3 4 6 5 1 3 7 dp[1] = 1 (LIS為3) dp[2] = 2 (LIS為3 4) dp[3] = 3 (LIS為3 4 6) dp[4] = 3 (LIS為3 4 5) dp[5] = 1 (LIS為1) dp[6] = 2 (LIS為1 3) dp[7] = 4 (LIS為3 4 5 7 or 3 4 6 7) 所以LIS長度為4 ## 實作範例 ```cpp= #include<bits/stdc++.h> using namespace std; int main(){ios::sync_with_stdio(0);cin.tie(0); int n; cin >> n; vector<int> a(n); for(int i = 0; i < n; i++){ cin >> a[i]; } vector<int> dp(n); fill(dp.begin(),dp.end(),1); for(int i = 0; i < n; i++){ for(int j = 0; j < i; j++){ if(a[j] < a[i]){ dp[i] = max(dp[j] + 1, dp[i]); } } } int ans = 0; for(int i = 0; i < n; i++){ ans = max(ans,dp[i]); } cout << ans << '\n'; } ``` ## 二分搜解法 當LIS[back] < a[i]:代表a[i]能接在LIS[back]後面增加長度 否則,找到 LIS中第一個大於等於a[i]的元素然後把這個元素替換成a[i] 因為a[i]替換後能在後面接更多元素 這個方法陣列中的數有可能不是正確的LIS(很有可能不是) 但長度會是對的 時機複雜度$O(nlogn)$ ## 實作 ```cpp= int main() { int n; while (cin >> n) { vector<int> v; for (int i = 0, x; i < n; i++) { cin >> x; if (!v.size() || x > v.back()) v.push_back(x); else *lower_bound(v.begin(), v.end(), x) = x; } cout << v.size() << '\n'; } } ``` # LCS $LCS$全名$Longest\ Common\ Subsequence$ 最常共同子序列 ## 解法 這題我們可以先考慮兩個字串的最後一個字元,並將問題分成四種狀況 假設現有兩個字串A B長度都是10,並組成一個二維陣列LCS代表不同子字串下LCS的長度 1. 如果最後兩字串的最後一個字元一樣,那LCS就是兩個字串分別去調最後一個字元的LCS LCS[9][9] = LCS[8][8] + 1 2. 如果最後兩個字串的最後一個字元只有一個字是包含在最佳解的 LCS[9][9] = LCS[9][8] or LCS[8][9] 根據以上2點可以發現,每格LCS都是由其上方、左方、左上方更新而來的 遞迴式: $LCS[i][j]=$ $\{max(LCS[i-1][j], LCS[i][j-1])\ \ ,A[i] != B[j]$ $\{LCS[i-1][j-1] + 1\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ,A[i] == B[j]$ 示意圖: ![image](https://hackmd.io/_uploads/BJNhckCfC.png) > 圖片來自下方演算法筆記,裡面有動畫模擬過程 > https://web.ntnu.edu.tw/~algo/Subsequence2.html 時間複雜度O(NM) ## 實作 ```cpp= #include <bits/stdc++.h> using namespace std; #define int long long signed main(){ string s1, s2; cin >> s1 >> s2; int n = s1.size(); int m = s2.size(); int dp[n+1][m+1]; for(int i = 0; i <= n; i++){ dp[i][0] = 0; } for(int i = 0; i <= m; i++){ dp[0][i] = 0; } for(int i = 1; i <= m; i++){ for(int j = 1; j <= n; j++){ if(s1[j-1] == s2[i-1]){ dp[j][i] = dp[j-1][i-1] + 1; } else{ dp[j][i] = max(dp[j-1][i], dp[j][i-1]); } } } cout << dp[n][m] << '\n'; } ``` ### 滾動DP ```cpp= #include <bits/stdc++.h> using namespace std; #define int long long signed main(){ string s1, s2; cin >> s1 >> s2; int n = s1.size(); int m = s2.size(); int dp[n+1][2]; for(int i = 0; i <= n; i++){ dp[i][0] = 0; dp[i][1] = 0; } int from = 0, to = 1; for(int i = 1; i <= m; i++){ for(int j = 1; j <= n; j++){ if(s1[j-1] == s2[i-1]){ dp[j][to] = dp[j-1][from] + 1; } else{ dp[j][to] = max(dp[j-1][to], dp[j][from]); } } swap(from, to); } cout << dp[n][from] << '\n'; } ```