线性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;
}
相关推荐
winlife_1 小时前
全程用 AI 做一款商业级手游 · EP10 道具系统:让三个按钮真正改变棋盘
windows·算法·unity·ai编程·游戏开发·mcp·玩法系统
计算机安禾1 小时前
【数据库系统原理】第16篇:范式理论(下):多值依赖与第四范式——消除非平凡的非函数依赖
算法
lqqjuly1 小时前
一致性模型深度解析
人工智能·深度学习·算法
光电笑映1 小时前
进程间通信:深入 System V IPC:共享内存、消息队列与信号量
linux·运维·服务器·c++
RisunJan1 小时前
Linux命令-patch (为开放源代码软件安装补丁程序)
linux·服务器·算法
a诠释淡然1 小时前
C++模板元编程—现代C++的黑魔法
开发语言·c++
汉克老师1 小时前
GESP2026年3月认证C++六级真题与解析(单选题1-8)
c++·多态··构造函数·循环队列·bst·gesp6级
一条大祥脚2 小时前
ABC460贪心|多源BFS|数论|计数|线段树|树的直径
算法·宽度优先
charlie1145141912 小时前
现代C++工程:constexpr 基础:编译期求值的艺术
开发语言·c++