动态规划-最长上升(下降)子序列的O(n logn)写法(基于树状数组)
常规最长上升(下降)子序列的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。
优化后可以达到 O ( n l o g n ) O(n logn) O(nlogn)
一般思维是遍历数组,每次枚举找出前面比它小的元素对应的最长上升子序列的最大长度,而树状数组正是在这一步优化速度的。
那么如何利用树状数组加速呢?
先复制一份数组,然后排序并剔除相同元素。新数组暂且命名为数组 b b b,原数组命名为数组 a a a。刚开始时树状数组是空的,然后从a数组第一项开始枚举,每次枚举找出它在数组 b b b中的位置 p p p,利用利用树状数组找出位置 p − 1 p -1 p−1 对应的最长上升子序列的长度 t t t,那么 t + 1 t + 1 t+1 就是以 a [ i ] a[i] a[i] 结尾的最大上升子序列的长度(标记部分看不懂先不管,后面会解释)。这样我们就完成了递推环节的其中一个环节。
然后利用 u p d a t e update update函数,把 t + 1 t + 1 t+1 传递到树状数组的 p p p位置,取最大值更新 T [ p ] T[p] T[p]的值。因为 b b b数组是有序的,我们按照元素在 b b b数组中的位置 p p p更新树状数组,这样树状数组中的元素也是从小到大有序排列的,且树状数组中的元素在 a a a数组中都在 a [ i ] a[i] a[i]前面,因为我们遍历a数组是从前往后遍历的,先访问到的元素先进树状数组。这样在树状数组中 p p p位置之前的元素都满足两个条件:
- 在 a a a数组中位于 a [ i ] a[i] a[i]之前;
- 值比 a [ i ] a[i] a[i]小。
这样只需要利用 q u e r y query query函数就很轻易找出到p-1为止的最长上升子序列的最大长度了。把它加上 1 1 1就是 p p p位置(即到 a [ i ] a[i] a[i]为止)的最长上升子序列的长度。
在 q u e r y query query函数调用后让得到的值加一后与 a n s ans ans比较,保持 a n s ans ans始终为最大值,这样递推完成后 a n s ans ans就是最终结果。
代码
#define lowbit(a) a&-a
int T[maxn];
int n;
void update(int x, int y) {//位置x变成y
while (x <= n) {
T[x] = max(T[x], y);
x += lowbit(x);
}
}
int query(int n) {//查询前n项的最大值
int ans = 0;
while (n > 0) {
ans = max(ans, T[n]);
n -= lowbit(n);
}
return ans;
}
int LIS(int a[], int n) {
memset(T, 0, sizeof(T));
int dp[maxn];
int b[maxn];
for (int i = 0; i < n; i++) b[i] = a[i];
sort(b, b + n);
int len = unique(b, b + n) - b;//找出数组内所有不重复的元素并排序
int ans = 1, t;
for (int i = 0; i < n; i++) {
int p = lower_bound(b, b + len, a[i]) - b + 1;//在所有元素中找到a[i]的排名
t = query(p - 1) + 1;/*由于目前已经入树状数组的元素都按照排序后的位置排列,
所以查询排序后p位置前的最大值即为a数组中到a[i]为止的最长上升子序列的长度*/
ans = max(ans, t);
update(p, t);//更新a[i]对应在树状数组中的值(即为到a[i]为止的最长上升子序列的长度)
}
return ans;
}
实例
#include <bits/stdc++.h>
#define maxn 1005
#define inf 0x3f3f3f3f
#define lowbit(a) a&-a
using namespace std;
int a[maxn];
int T[maxn];
int n;
void update(int x, int y) {//位置x变成y
while (x <= n) {
T[x] = max(T[x], y);
x += lowbit(x);
}
}
int query(int n) {//查询前n项的最大值
int ans = 0;
while (n > 0) {
ans = max(ans, T[n]);
n -= lowbit(n);
}
return ans;
}
int LIS(int a[], int n) {
memset(T, 0, sizeof(T));
int dp[maxn];
int b[maxn];
for (int i = 0; i < n; i++) b[i] = a[i];
sort(b, b + n);
int len = unique(b, b + n) - b;//找出数组内所有不重复的元素并排序
int ans = 1, t;
for (int i = 0; i < n; i++) {
int p = lower_bound(b, b + len, a[i]) - b + 1;//在所有元素中找到a[i]的排名
t = query(p - 1) + 1;/*由于目前已经入树状数组的元素都按照排序后的位置排列,
所以查询排序后p位置前的最大值即为a数组中到a[i]为止的最长上升子序列的长度*/
ans = max(ans, t);
update(p, t);//更新a[i]对应在树状数组中的值(即为到a[i]为止的最长上升子序列的长度)
}
return ans;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
cout << LIS(a, n) << "\n";
return 0;
}