从偏序问题的角度对LIS进行优化 | 豆包MarsCode AI刷题

65 阅读4分钟

前言

在做 6. 小E的怪物挑战 时,重新从一个新的角度去理解了LIS问题(最长上升子序列问题),并使用了树状数组去优化,达到了和二分优化相同的时间复杂度,并可以运用到其他更多的题目上去,在这里分享给大家。

我们首先回顾一下这个题目。

最长上升子序列II

题目描述

给定一个长度为 N 的数列 Ai,求数值严格单调递增的子序列的长度最长是多少。

输入格式

第一行包含整数 N 。

第二行包含 N 个整数,第 i 个整数为 Ai, 表示完整序列。

输出格式

输出一个整数,表示最大长度。

数据范围 1 ≤ N ≤ 100000, −10^9 ≤ Ai ≤ 10^9。

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

树状数组做法(二维偏序)

大部分人理解该问题基本都是用的二分的方法,这里提供从偏序问题的角度解决LIS问题的基本思路,并简述二维偏序问题。

在讲述这个方法之前,我们先来简述一个概念:偏序

偏序

简单地说,偏序就是满足【自反性】【反对称性】【传递性】的一种元素之间的关系。

举个例子:如果a,b,c是一个偏序集中的元素。

  1. 自反性: a≤a显然成立
  2. 反对称性: 若 a≤bb≤a,那么 a=b
  3. 传递性: 若 a≤bb≤c,那么 a≤c 显然成立

很显然,我们有理数上的小于等于关系就是一个典型的偏序关系,我们也将其称之为一维偏序。

在算法竞赛里,我们主要需要针对传递性进行考虑。

二维偏序

在刚刚一维偏序的例子里,我们可以发现a,b,c三个元素的偏序关系,实际上就是实数轴上的三点 a,b,c 的相对位置关系,越小的数越靠左,越大的数越靠右。

知道一维偏序实际上是数轴上的位置关系以后,我们现在来了解二维偏序。

<a1,b1>,<a2,b2>都是偏序,且(a1,b1)(a2,b2)分别是两个点对,则该问题就是二位偏序。

具体一点来说:如果我们在平面上有两点(x1,y1)(x2,y2),其中x1x2之间有横轴上的位置关系,y1y2有纵轴上的位置关系,这两点的关系我们就称之为二维偏序。如果(x1,y1)(x2,y2)左下角,点1就同时满足了x和y轴两个偏序中均小于点2。

在一维偏序中,a,b两个元素只有:a在b左侧,重合,a在b右侧3种情况。 在二维偏序中,共有左上,上,右上,左,重合,右,左下,下,右下共9种情况。

回到本题

考虑最朴素的DP方式:

dp[i] = max(dp[j]) + 1,其中 j < i 且 a[j] < a[i]

我们发现,如果我们将下标i,j看成纵轴,a[i],a[j]看作横轴,将其画在平面上得到两点(a[i],i)(a[j],j),我们发现,动态规划的转移方程,实际上是要我们在点(i,a[i])的左下角区域的所有(a[j],j)中,找到一个最大的dp[j]进行转移。

我们考虑将问题离线化,将pair(a[i],i)排序后,每次查询前,先单点修改,将所有a[j] < a[i]点的dp[j] 值写入线段树/树状数组的 j 下标处,然后我们区间查询线段树/树状数组中下标 1~i-1 中的最大值,即为我们要找的dp[j]。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

const int N = 2e5+5;

int n;
int tr[N];
PII a[N];
int ans[N];

int lowbit(int x) {
	return x & -x;
}

void add(int x, int pos) {
	while(pos <= n) {
		tr[pos] = max(tr[pos], x);
		pos += lowbit(x);
	}
}

int query(int pos) {
	int res = 0;
	while(pos) {
		res = max(tr[pos], res);
		pos -= lowbit(x);
	}
	return res;
}

int main() {
	cin >> n;
	
	for(int i=1;i<=n;i++) {
		cin >> a[i].first;
		a[i].second = -i;
	}
	sort(a+1,a+1+n);
	int res = 0;
	for(int i=1;i<=n;i++) {
		auto [x, idx] = a[i];
		idx = -idx;
		ans[idx] = query(idx) + 1;
		add(ans[idx], idx);
		res = max(res, ans[idx]);
	}
	
	cout << res;
	
	return 0;
}