LIS最長上升子序列問題+記錄路徑
阿新 • • 發佈:2020-12-09
\(O(nlogn)\)LIS
\(nlogn\)做法
維護\(dp\)陣列使得每次加入元素,如果是大於\(dp\)陣列尾部,那麼直接加到最後面,如果不是,那麼加入到對答案影響最好的位置,就是嚴格大於的下一個位置,插入時用二分查詢可降低至\(log\)。
如果要記錄路徑,那麼就可以每次從\(a\)數組裡加入到\(dp\)中時,可以記錄下\(a[i]\)的位置,然後從\(n\)倒著遍歷\(a\)陣列,將最先遇到的符合答案位置的\(a[i]\)元素加入到答案陣列中。
證明
我覺得還是證明一下這樣做的正確性。
首先得知\(dp\)陣列在\(O(nlogn)\)的做法當中所儲存的並不是最長上升子序列,而是對答案最優的狀態。
\(dp\)數組裡雖然是上升序列,但是它卻是會由後部分的數插入到\(dp\)從而使\(dp\)更有利於答案,但是同時改變了數的位置,導致後插的數到了前面。
首先確定的是,\(dp\)數組裡的數一定是遞增的並且是最長的,並且一定全部最長公共子序列中的全部元素一定曾經在裡面呆過,我們的\(dp\)陣列唯一就差在把元素位置弄亂了,那麼我們每次將\(a\)元素插入的時候,一定有對應的位置對於\(dp\)陣列,然後我們就記錄\(a\)對應\(dp\)的位置,然後從後往前,先遇到的先匹配,一定避免了所得答案不是\(a\)陣列原順序的情況,然後就會得到某個最長公共子序列。
記錄路徑的例題HDU 1160
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1009; int dp[N]; struct node { int w, s, idx; bool operator<(node a) const { if (a.s == s )return a.w < s; return a.s < s; } }a[N]; int main() { int n = 1; while (cin >> a[n].w >> a[n].s) a[n].idx = n,n++; n--; sort(a + 1, a + 1 + n); int ans[1100]{}; int pos[1100]{}; int len = 1;dp[len] = a[1].w; ans[len] = 1; for (int i = 1; i <= n; i++) { if (a[i].w > dp[len]) { dp[++len] = a[i].w; pos[i] = len; } else { int p = lower_bound(dp + 1, dp + 1 + len, a[i].w) - dp; dp[p] = a[i].w; pos[i] = p; } } int nn = len; for (int i = n; i >= 1; i--) { if (pos[i] == len) { ans[len] = a[i].idx; len--; } } cout << nn << endl; for (int i =1; i <= nn; i++) { cout << ans[i] << endl; } }