# LCS longest common subsequence dp的精神:從後往前考慮,每次考慮最後一個的情況 ## 想法 Consider two sequence a and b > If a[i]==b[j], it is better to choose, so dp[i][j]=dp[i-1][j-1]+1 > If a[i]! =b[j], can't choose any for lcs, drop a[i] or b[j], so dp[i][j]=max(dp[i-1][j],dp[i][j-1]) ```cpp= int lcs(int i=n,int j=m){ if(i<0 || j<0) return 0; //out of range int chose=0,not_chose=0; if(a[i]==b[j]) chose=lcs(i-1,j-1)+1; //選擇當lcs not_chose=max(lcs(i-1,j),lcs(i,j-1)); //選擇丟哪個元素會對答案比較有幫助 return max(chose,not_chose); //選擇最佳解 } ``` **Top-down** 但由於這樣做太慢了所以我們可以記錄算過的答案,下次遇到就直接回傳算完的答案,也就是標準的dp的top-down做法 > 如果之前有算過就直接回傳之前的答案 > 如果沒有計算過則往下計算,並把答案存起來,再回傳 ```cpp= int lcs(int i=n,int j=m){ if(i<0 || j<0) return 0; if(ans[i][j]!=-1) return ans[i][j]; //如果之前有算過答案就直接回傳 int chose=0,not_chose=0; if(a[i]==b[j]) chose=lcs(i-1,j-1)+1; not_chose=max(lcs(i-1,j),lcs(i,j-1)); ans[i][j]=max(chose,not_chose); //先把答案存起來給之後查詢 return ans[i][j]; } ``` **Botton-up** DP的另一種寫法:bottom-up,直接改用迴圈,可省去遞迴呼叫的時間 > 由於每次都會往前面(i-1 or j-1)遞迴,所以迴圈寫法就是先把前面的算好,從下往上計算,故稱bottom-up 注意:不能用0-based,因為ans在一開始跑時(i-1 or j-1)會出界 ```cpp= for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ int chose=0,not_chose=0; if(a[i]==b[j]) chose=ans[i-1][j-1]+1; not_chose=max(ans[i-1][j],ans[i][j-1]); ans[i][j]=max(chose,not_chose); } } ``` 最後答案就是放在ans[n][m]這格 ## 加速(NlogN) 使用技巧:lcs轉lis Step1. 紀錄b陣列中元素值為x的所有index Step2. 依照a陣列中的順序,依序把該元素在b中的index反向(大到小)加到c陣列裡 Step3. 求c的lis,及a跟b的lcs ```cpp= int n,m; while(cin >> n >> m && n && m){ vector<int> a(n),b(m); for(auto &i:a) cin >> i; for(auto &i:b) cin >> i; //Step 1 vector<vector<int>> idx(MAXN); for(int i=0;i<m;i++){ idx[b[i]].push_back(i); } //Step 2 vector<int> c; for(int i=0;i<n;i++){ for(auto it=idx[a[i]].rbegin();it!=idx[a[i]].rend();it++){ c.push_back(*it); } } //Step 3 vector<int> lis; for(auto i:c){ if(lis.empty() || lis.back()<i) lis.push_back(i); else *lower_bound(all(lis),i)=i; } cout << lis.size() << '\n'; //lis=lcs } ```