动态规划-最长上升(下降)子序列的O(n logn)写法(基于树状数组)

动态规划-最长上升(下降)子序列的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 p1 对应的最长上升子序列的长度 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;
}

传送门:动态规划-最长上升(下降)子序列的O(n logn)写法(基于二分)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值