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