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


原文地址:https://www.cnblogs.com/Xiao-yan/p/14107825.html