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


posted @ 2020-12-09 12:03  u_yan  阅读(343)  评论(0编辑  收藏  举报