📌  相关文章
📜  为具有重复元素的数组获得严格的 LIS 所需的最小串联 |组 2(1)

📅  最后修改于: 2023-12-03 15:36:01.151000             🧑  作者: Mango

为具有重复元素的数组获得严格的 LIS 所需的最小串联 |组 2

当我们需要在具有重复元素的数组中获得严格的最长递增子序列(LIS)时,常常需要一些特殊的技巧,本文将介绍一种常用的解决方案。

解法思路

我们定义 $\text{LIS}(i)$ 表示以第 $i$ 个元素为结尾的最长递增子序列的长度,显然 $\text{LIS}(i)$ 与前面所有比它小的元素有关,于是我们可以考虑使用动态规划来解决。

具体来说,我们从前往后枚举数组中的每一个元素 $a_i$,对于每个 $i$,我们维护一个数组 $f_j$ 表示以 $a_j$ 结尾的长度为 $j$ 的最长递增子序列的最小结尾元素值。

对于当前的 $a_i$,我们可以将它加入到 $f_{\text{LIS}(i-1)+1}$ 中,如果 $f_{\text{LIS}(i-1)} < a_i$ 则将 $f_{\text{LIS}(i-1)+1} = a_i$,否则我们可以使用二分查找的方法找到最小的 $j$ 满足 $f_j \geq a_i$ 并更新 $f_{j+1} = a_i$。

这样,我们就可以 $O(n\log n)$ 时间复杂度内找到整个数组的最长递增子序列。

但是,由于数组中可能存在重复元素,我们需要在更新 $f_{j+1}$ 的时候注意一些细节,具体包括:

  1. 对于 $a_i = f_j$ 的情况,我们需要直接跳过,并将 $\text{LIS}(i)$ 设为 $\text{LIS}(i-1)$;
  2. 对于 $a_i < f_j$ 的情况,我们可以证明此时一定不会出现更好的递增子序列,因此我们同样可以将 $\text{LIS}(i)$ 设为 $\text{LIS}(i-1)$。

最后,我们只需要输出 $f$ 数组的前 $\text{LIS}(n)$ 项即可获得最长递增子序列。

代码实现

下面是具体的代码实现(使用 C++ 语言):

#include <iostream>
#include <vector>
using namespace std;

vector<int> get_lis(const vector<int>& a) {
    int n = a.size();
    vector<int> f(n + 1);
    vector<int> last(n + 1);
    last[1] = f[1] = a[0];
    int lis = 1;
    for (int i = 1; i < n; i++) {
        int pos = upper_bound(last.begin() + 1, last.begin() + lis + 1, a[i]) - last.begin();
        if (a[i] == f[pos - 1])
            continue;
        if (pos > lis)
            last[++lis] = a[i];
        else
            last[pos] = a[i];
        f[pos] = a[i];
    }
    return vector<int>(f.begin() + 1, f.begin() + 1 + lis);
}

其中,函数 get_lis(a) 接受一个整数数组 a 并返回它的最长递增子序列。