线性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;
}
相关推荐
先吃饱再说5 小时前
判断回文字符串,从一行代码到双指针优化
算法
见过夏天5 小时前
C++ 基础入门完全指南
c++
黄敬峰7 小时前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术9 小时前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六12 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术13 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize13 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考1 天前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
美团技术团队1 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法