【刷题日记】经典问题:导弹拦截(最长严格递增子序列动态规划)

P1020 [NOIP 1999 提高组] 导弹拦截

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

一行,若干个整数,中间由空格隔开。

输出格式

两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入输出样例 #1

输入 #1

389 207 155 300 299 170 158 65

输出 #1

6
2

说明/提示

对于前 \(50\%\) 数据(NOIP 原题数据),满足导弹的个数不超过 \(10^4\) 个。该部分数据总分共 \(100\) 分。可使用\(\mathcal O(n^2)\) 做法通过。
对于后 \(50\%\) 的数据,满足导弹的个数不超过 \(10^5\) 个。该部分数据总分也为 \(100\) 分。请使用 \(\mathcal O(n\log n)\) 做法通过。

对于全部数据,满足导弹的高度为正整数,且不超过 \(5\times 10^4\)

NOIP1999 提高组 第一题

题解

第一步思考:01DP的状态设计

对于第一问,我们要选择出一些导弹进行拦截。每一个导弹有拦截、不拦截两种选择。符合01DP的场景。

01DP时,每个对象有三个数据:i, a[i], ans[i]。

此题中,每个导弹有:导弹编号i,导弹高度a[i],能拦截的导弹数ans[i]。

思考以导弹编号i为状态,dp[i][ans]表示目前为止能拦截的最多导弹数量为ans时,已拦截的导弹中最低的高度,时间复杂度为O(\(n^2\)),无法优化。

思考以能拦截的导弹数ans为状态,dp[ans]表示拦截ans数量的导弹时,已拦截的导弹中最低的高度。比如对于输入1 7 3 2,dp[1] = 7(只需要拦截1个导弹,选择尽可能高的导弹可以便于后续继续拦截,故最低高度为7),dp[2] = 3(选择拦截高度为7和3的导弹为最佳方案),dp[3] = 2。

第二步思考:状态转移

这时,又来了一颗高度为4的导弹,如何更新dp?4 < dp[1]=7,所以这颗导弹可以跟在只拦截一个导弹的情况后面接着拦截,即拦截完高度为7的导弹,再拦截高度为4的导弹。这个时候,拦截了两颗导弹,最低高度为4,更新dp[2] = max(dp[2], 4)dp[2] = 4。(解释:拦截"7->4"比"7->3"更好,因为后续更容易拦截尽可能多的导弹)。dp[3]需要更新吗?不需要,因为原先拦截两颗导弹的所有情况中,最低导弹高度为3,比4还要低,那4就无法成为第三颗被拦截的导弹,dp[3]不需要更新。

思考:然后又来了一颗高度为9的导弹,如何更新dp?

更新dp[1] = 9dp[2]需要更新吗?不需要。

然后的然后,又来了一颗高度为1的导弹,如何更新dp?

1 < dp[1]=9,可以看dp[2]。1 < dp[2]=4,可以看dp[3]。1 < dp[3]=2,可以看dp[4]。dp[4] = 0(其实目前没有dp[4]),那就让dp[4] = 1,表示新来的这颗高度为1的导弹可以作为第四颗被拦截的导弹

第三步思考:优化时间复杂度

目前我们对每个导弹进行一次更新,每次更新从dp[1]开始往上找,只要dp[ans] > a[i]就继续,让ans++去看下一个dp能不能更新。时间复杂度为O(\(n^2\))。
我们发现,每次都需要找到dp[ans]使得dp[ans]刚好是dp数组中第一个比a[i]小的。而规律是:dp[i] >= a[i](i < ans), dp[ans] > dp[j](j > ans),符合二分的规律。用二分优化后,时间复杂度为O(\(nlog(n)\))。

第四步思考:代码实现

C++中自带的upper_bound()函数可以实现在递增数列中找到第一个大于x的位置。但我们需要一个递增数列,将x变为-x存储即可。

auto it = upper_bound(dp1.begin(), dp1.end(), -x);	//二分找到需要更新的位置
if(it == dp1.end())	dp1.push_back(-x);		//如果需要加长dp数组
else	*it = -x;					//进行更新

第五步思考:第二问怎么做

第一问,我们求出了最长非严格递减子序列的长度。

第二问,我们需要知道至少需要多少装置,才能将全部导弹拦截。

狄尔沃斯定理:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。

在这里,我们用多个”拦截装置“把导弹划分为很多条链,并且每一条都是非严格递减的。第二问要求的最少装置数量,就是最小链划分中,链的数目。(补充离散知识:最小链划分就是将有限偏序集划分成尽可能少的多条链)根据狄尔沃斯定理,这个数目与最大反链的元素数目相同。(补充离散知识:反链中的任意两个元素都没有偏序关系)

这道题中,我们定义偏序关系为:若 \(i<j\)\(a_i>=a_j\) ,则 \(i⪯j\)。(即两个导弹可以被同一系统拦截)。反链中任意两元素没有偏序关系,即若 \(i<j\)\(a_i<a_j\)。即第二问实际上问的就是最长严格递增子序列的长度。

AC代码

AC代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

i64 x;
vector<i64>dp1, dp2;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	while(cin >> x){
		auto it = upper_bound(dp1.begin(), dp1.end(), -x);	//二分找到需要更新的位置
		if(it == dp1.end())	dp1.push_back(-x);				//如果需要加长dp数组
		else	*it = -x;									//进行更新
		it = lower_bound(dp2.begin(), dp2.end(), x);
		if(it == dp2.end())	dp2.push_back(x);
		else	*it = x;
	}
	cout << dp1.size() << '\n' << dp2.size();
	return 0;
}

练习题

Sequence Decomposing

You are given a sequence with N integers: A={A1,A2,⋯,AN}. For each of these N integers, we will choose a color and paint the integer with that color. Here the following condition must be satisfied:

If Ai and Aj(i<j) are painted with the same color, Ai<Aj.

Find the minimum number of colors required to satisfy the condition.

Input is given from Standard Input in the following format:

N
A1
:
AN
where 1≤N≤105,0≤Ai≤109
.

Print the minimum number of colors required to satisfy the condition.

Sample Input 1

5
2
1
4
5
3

Sample Output 1

2

Sample Input 2

4
0
0
0
0

Sample Output 2

4

AC代码

AC代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

i64 n, x;
vector<i64>dp;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for(i64 i = 0;i<n;i++){
		cin >> x;
		x = -x;
		auto it = upper_bound(dp.begin(), dp.end(), x);
		if(it == dp.end())	dp.push_back(x);
		else	*it = x;
	}
	//for(auto i:dp)	cout << i << endl;
	cout << dp.size();
	return 0;
}
posted @ 2025-07-14 16:42  Alkaid16  阅读(60)  评论(0)    收藏  举报