1. 程式人生 > 實用技巧 >LIS最長上升子序列問題+記錄路徑

LIS最長上升子序列問題+記錄路徑

\(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;
    }
}