线性dp-LIS题目5(导弹拦截,二分优化)

题目链接

这题有两问:①这套系统最多能拦截多少导弹(求最长不上升子序列);②拦截所有导弹最少要配备多少套这种导弹拦截系统(求最长上升子序列)。

1 朴素解法

数据规模达到105,朴素解法的两重循环会超时。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e5 + 5;
int dp1[maxn], dp2[maxn], a[maxn];

signed main() {
	int n = 0;
	while (cin >> a[++n]) {
		dp1[n] = 1;
		dp2[n] = 1;
		for (int i = 1; i < n; i++)
			if (a[i] >= a[n])
				dp1[n] = max(dp1[i] + 1, dp1[n]);
		for (int i = 1; i < n; i++)
			if (a[i] < a[n])
				dp2[n] = max(dp2[i] + 1, dp2[n]);
	}
	int ans1 = 0, ans2 = 0;
	for (int i = 1; i < n; i++) {
		ans1 = max(ans1, dp1[i]);
		ans2 = max(ans2, dp2[i]);
	}
	cout << ans1 << endl << ans2;
	return 0;
}

2 优化分析

2.1 朴素解法的缺陷

朴素解法,以求最长上升子序列为例:

cpp 复制代码
for (int i = 1; i < n; i++)
			if (a[i] < a[n])
				dp2[n] = max(dp2[i] + 1, dp2[n]);

上面的核心代码通过找比 a n an an小且在 a n an an之前的数 a i ai ai,看看 a n an an接在 a i ai ai后面会不会比 a n an an之前的最长上升子序列长,如果是,则把 a n an an接在 a i ai ai后面,更新 d p 2 n dp2n dp2n的值,否则就不接。

这里的枚举会有很多没必要的比较,例如数据:1,2,3,4。对于4很明显接在3后面构成子序列1,2,3,4是最优解。在核心代码里面要和1,2,3比较, d p 2 1 dp21 dp21和 d p 2 2 dp22 dp22应该都是小于 d p 2 3 dp23 dp23的,因此会比较浪费时间。

2.2 优化思路

我们可以先排出一个上升子序列各个位置的较优数。比如,这组数65,170,155,300,299,158,207,389。一开始,上升序列什么都没有,第一个数是65,暂时把它放在第1位,我们可以开一个数组b,令 b 1 = 65 b1=65 b1=65;再看第二个数170,可以接在65后面,令 b 2 = 170 b2=170 b2=170;接着是第三个数155,比170小,不能往后接。但是,与170相比,把155作为上升子序列第二个数接在65后面,对于后面的数来说,可能更好接,形成更长的上升子序列,于是令 b 2 = 155 b2=155 b2=155。再接着看第四个数300,可以接在 b 2 b2 b2后面,令 b 3 = 300 b3=300 b3=300。之后看第五个数299,不能接在 b 3 b3 b3后面,但是,与300想比,把299接在65,155后面会更好,令 b 3 = 299 b3=299 b3=299。在之后第六个数158,同理,不能接在299后面,但是接在158后面,形成序列65,155,158对于后面的数延展更长的上升子序列更友好,故令 b 3 = 158 b3=158 b3=158。我们再看第七个数207,比158大,往后接, b 4 = 207 b4=207 b4=207。最后一个数389,比207大,就接在207后面, b 5 = 389 b5=389 b5=389。

概括一下刚才找最长上升子序列的过程,如果是第一个数,直接让他成为上升子序列的第一个数。如果不是,我们要先找到第一个大于等于当前的数的数,在上升子序列中,让当前的数替代第一个比它大或等于它的数。如果这个数比子序列的最大数还要大,就直接接在后面。b数组的下标即为最长上升子序列的长度。

同理,如果找的是最长不下降子序列,如果是第一个数,直接让他成为上升子序列的第一个数。如果不是,我们要先找到第一个比当前的数大的数,在上升子序列中,让当前的数替代第一个比它大的数。如果这个数比子序列的最大数还要大或等于子序列的最大数,就直接接在后面。b数组的下标即为最长上升子序列的长度。

这个过程中,设计在b数组中做查找。如果一个个遍历可能会超时,而我们观察到b数组是有序的,可以考虑二分查找。求最长上升子序列,查找的核心代码:

cpp 复制代码
int l = 1, r = lenn;
				while (l <= r) {
					int mid = l + r >> 1;
					if (a[n] > b[mid])
						l = mid + 1;
					else
						r = mid - 1;
				}
				b[l] = a[n];

这个代码 l l l和 r r r都是可以取到的。比如一组数100,130,90,一开始 l = 1 , r = l e n n = 0 l=1,r=lenn=0 l=1,r=lenn=0,就得到 b 1 = 100 b1=100 b1=100;之后是130,一开始 l = 1 , r = 1 , m i d = 1 l=1,r=1,mid=1 l=1,r=1,mid=1,130>100, l = 2 l=2 l=2,跳出while循环, b 2 = 130 b2=130 b2=130。最后是90,一开始 l = 1 , r = 2 , m i d = 1 l=1,r=2,mid=1 l=1,r=2,mid=1,90<100, r = m i d − 1 = 1 r=mid-1=1 r=mid−1=1,说明区间在 m i d mid mid左边。接着是 l = 1 , r = 1 , m i d = 1 l=1,r=1,mid=1 l=1,r=1,mid=1,90<100, r = m i d − 1 = 0 r=mid-1=0 r=mid−1=0,结束while循环, b 1 = 90 b1=90 b1=90。

又如100,110,120,130,一开始 l = 1 , r = l e n n = 0 l=1,r=lenn=0 l=1,r=lenn=0,就得到 b 1 = 100 b1=100 b1=100;之后是110,一开始 l = 1 , r = 1 , m i d = 1 l=1,r=1,mid=1 l=1,r=1,mid=1,110>100, l = 2 l=2 l=2,跳出while循环, b 2 = 110 b2=110 b2=110。之后是120,一开始 l = 1 , r = 2 , m i d = 1 l=1,r=2,mid=1 l=1,r=2,mid=1,120>100, l = m i d + 1 = 2 l=mid+1=2 l=mid+1=2,说明区间在 m i d mid mid右边。接着是 l = 2 , r = 2 , m i d = 2 l=2,r=2,mid=2 l=2,r=2,mid=2,120>110, l = m i d + 1 = 3 l=mid+1=3 l=mid+1=3,然后比较 l l l和 r r r结束while循环, b 3 = 120 b3=120 b3=120。最后看130,一开始 l = 1 , r = 3 , m i d = 2 l=1,r=3,mid=2 l=1,r=3,mid=2,130>110, l = m i d + 1 = 3 l=mid+1=3 l=mid+1=3。接着 l = 3 , r = 3 , m i d = 3 l=3,r=3,mid=3 l=3,r=3,mid=3,130>120, l = m i d + 1 = 4 l=mid+1=4 l=mid+1=4,然后比较 l l l和 r r r结束while循环, b 4 = 130 b4=130 b4=130。

3 AC Code

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int n, dp[100005], a[100005], b[100005], c[100005];

int main() {
	int lenn = 0, leno = 0;
	c[leno] = 1e9;
	while (cin >> a[++n]) {
		if (a[n]) {
			if (a[n] <= c[leno]) {
				++leno;
				c[leno] = a[n];
			} else {
				int l = 0, r = leno;
				while (l <= r) {
					int mid = l + r >> 1;
					if (a[n] <= c[mid])
						l = mid + 1;
					else
						r = mid - 1;
				}
				c[l] = a[n];
			}
			if (a[n] > b[lenn]) {
				++lenn;
				b[lenn] = a[n];
			} else {
				int l = 1, r = lenn;
				while (l <= r) {
					int mid = l + r >> 1;
					if (a[n] > b[mid])
						l = mid + 1;
					else
						r = mid - 1;
				}
				b[l] = a[n];
			}
		}
	}
	cout << leno << endl;
	cout << lenn;
	return 0;
}
相关推荐
_wyt0013 分钟前
多重背包问题详解
c++·背包dp
Ljwuhe1 小时前
C++——多态
开发语言·c++
越甲八千1 小时前
STL stack为何没有迭代器
c++
CV-Climber2 小时前
检索技术的实际应用
人工智能·算法
hhzz2 小时前
Tiger AI Platform平台中增加人脸识别功能
图像处理·人工智能·算法·计算机视觉·大模型
从零开始的代码生活_3 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法
云小逸3 小时前
【C++ 第七阶段:模板、泛型编程与工程综合详解】
开发语言·c++
TsingtaoAI3 小时前
3D高斯泼溅技术发展及其在具身智能领域的应用综述
人工智能·算法·ai·具身智能·高斯泼溅
atunet3 小时前
关于图算法中的连通分量检测与最小割问题7
算法
GIS阵地4 小时前
QgsSingleBandPseudoColorRenderer 完整详解(QGIS 3.40.13 C++)
开发语言·前端·c++·qt·qgis