模板

原来B序列并不是最长上升子序列,虽然他确实是上升的,但是顺序不一定对(指它不一定是原序列的一个子序列)。

最长上升子序列

模板。

int LIS() {
    int len = 1;
    B[1] = A[1];
    for(int i = 2; i <= n; ++i) {
        if(A[i] > B[len]) {
            B[++len] = A[i];
        } else {
            int pos = lower_bound(B + 1, B + 1 + len, A[i]) - B;
            B[pos] = A[i];
        }
    }
    return len;
}

最长不下降子序列

把上面的大于换成大于等于,把大于等于(lower_bound())换成大于(upper_bound())。

int LIS() {
    int len = 1;
    B[1] = A[1];
    for(int i = 2; i <= n; ++i) {
        if(A[i] >= B[len]) {
            B[++len] = A[i];
        } else {
            int pos = upper_bound(B + 1, B + 1 + len, A[i]) - B;
            B[pos] = A[i];
        }
    }
    return len;
}

构造

例题

CF - 1296E2

题目链接https://codeforces.com/contest/1296/problem/E2

这里要求最长下降子序列的长度,并划分为若干个不下降子序列。于是把数字全部取反,转化成求最长上升子序列的长度,并划分为若干个不上升的子序列。

考虑 (B[x]) 的意思是当前字符串长度为 (x) 的上升子序列的最优结尾字符,在lower_bound()找到 (pos) 之后就说明 (A[i]) 这个字符是可以替换掉 (B[pos]) 原本的字符的,所以在进行链划分的时候可以直接接在这个字符的后面!

int n;
char s[200005];
int A[200005];
int B[200005];
int id[200005];
int C[200005];

int LIS() {
    int len = 1;
    B[1] = A[1];
    C[1] = 1;
    id[1] = 1;
    for(int i = 2; i <= n; ++i) {
        if(A[i] > B[len]) {
            B[++len] = A[i];
            C[i] = len;
            id[len] = i;
        } else {
            int pos = lower_bound(B + 1, B + 1 + len, A[i]) - B;
            B[pos] = A[i];
            C[i] = C[id[pos]];
            id[pos] = i;
        }
    }
    return len;
}

void test_case() {
    scanf("%d%s", &n, s + 1);
    for(int i = 1; i <= n; ++i)
        A[i] = 'z' + 1 - s[i];
    int len = LIS();
    printf("%d
", len);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", C[i], " 
"[i == n]);
}

从这里可以得到启发,每次找到一个位置之后都往接在原来的后面,在得到最长上升子序列的同时就会得到一个不下降子序列的链划分。且把这些链的末端连起来应该就是最长上升子序列。

参考资料

https://blog.csdn.net/lxt_Lucia/article/details/81206439
https://www.cnblogs.com/yuelian/p/8745807.html

原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12294114.html