LIS的长度 及 输出结果
首先来看一道经典例题吧(导弹拦截)
题意
\(n\) 个导弹依次飞来,每次拦截的导弹都不能高于前一发的高度,求一次最多可以拦截多少导弹
题目数据范围:\(n \leq 10^5\)
求LIS的长度
\(O(n^2)\) 做法
用 \(dp[i]\) 表示拦截第 \(i\) 枚导弹的情况下,最多拦截导弹的个数
容易想到转移 \(dp[i] =\) max{ $ dp[j] + 1 $ }
容易看出,时间复杂度是 \(O(n^2)\) 的
\(O(nlogn)\) 做法
用二分查找维护另一个序列 \(b[]\)
具体见代码:
void solve() {
for(int i = 1; i <= n; i++) {
tem[i] = a[i];
}
for(int i = 1; i <= n; i++) {
a[i] = tem[n + 1 - i];
}
// 最长不下降子序列
len = 0;
b[++len] = a[1];
for(int i = 2; i <= n; i++) {
if(a[i] >= b[len]) {
b[++len] = a[i];
}
else {
int id = upper_bound(b + 1, b + len + 1, a[i]) - b;
b[id] = a[i];
}
}
cout << len << endl;
// 最长上升子序列
for(int i = 1; i <= n; i++) {
a[i] = tem[i];
}
len = 0;
b[++len] = a[1];
for(int i = 1; i <= n; i++) {
if(a[i] > b[len]) {
b[++len] = a[i];
}
else {
int id = lower_bound(b + 1, b + len + 1, a[i]) - b;
b[id] = a[i];
}
}
cout << len << endl;
}
但是注意,这里的 \(b[]\) 存的并不是真正的最长子序列,只有长度是正确的
输出LIS具体序列
上面的求长度的两种方法就行不通啦。
再引入一个数组 \(pos[]\),存储每个数被放进 \(b[]\) 数组时所在的位置
然后就可以输出合法的LIS具体序列
例如,要在求出最长LIS的同时,需要求出最短的包含这个LIS的区间长度
// 第一个问题:最长序列的长度
// 第二个问题:输出最长的序列(同时要求包含这个序列的区间长度最短)
void solve() {
len = 0;
b[++len] = a[1];
pos[1] = 1;
v[1].push_back(1);
for(int i = 2; i <= n; i++) {
if(a[i] >= b[len]) {
b[++len] = a[i];
pos[i] = len;
}
else {
int id = upper_bound(b + 1, b + len + 1, a[i]) - b;
b[id] = a[i];
pos[i] = id;
}
// cout << i <<' '<<a[i]<<' '<<pos[i]<<endl;
v[pos[i]].push_back(i);
}
printf("%d ", len);
int ans = 1e9;
for(auto i : v[len]) { // i :
int pos = i;
for(int j = len - 1; j >= 1; j--) {
int p = lower_bound(v[j].begin(), v[j].end(), pos) - v[j].begin();
p -= 1;
pos = v[j][p];
}
ans = min(ans, i - pos + 1);
}
printf("%d\n", ans);
}

浙公网安备 33010602011771号