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