8.18习题随记
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\)。
此外本题开启 spj,每点两问,按问给分。
NOIP1999 提高组 第一题
\(\text{upd 2022.8.24}\):新增加一组 Hack 数据。
分析
这是最长下降/上升子序列
不难想到这道题目的\(O(n^2)\)做法:
dp[i]:以第i枚导弹为结束时的导弹个数,再枚举前面是否满足 $ >or< $ 的条件。
但\(10^5\)的数据量卡死
接下来\(O(n log n)\)的做法:
通过观察,我们可以发现,再最长非增子序列中,序列越长则末尾最大值越小,
设\(f[i]\):长度为i的子序列末尾之最大值
例如这里 2 8 5 9 4 f[1]:9 f[2]:5 f[3]:4 ......
所以\(f\)数组具有单调性,在这里单调下降
由于我们在选择下一个导弹高度a[i]时,只需考虑长度为i的子序列末尾之最大值即可,只要大于就可以“拼接”
形象图>> f[1]:9 f[2]:5 f[3]:4
a[i]=8拼这 a[i]=3拼这
愉快得出代码
auto it=upper_bound(f+1,f+ans+1,a[i],greater<int>());
int last=it-f-1;
int x=last+1;
ans=max(ans,x);
f[x]=a[i];
注意了
last 是通过二分查找到的位置,表示 a[i] 可以接在长度为 last 的子序列后面
因此,a[i] 可以形成一个新的长度为 x = last + 1 的子序列
代码
#include<bits/stdc++.h>
using namespace std;
const int U=1e5+5; // 定义数组最大大小
typedef long long ll; // 定义长整型别名
int a[U]; // 存储输入序列的数组
int dp[U], dp2[U]; // 未使用的DP数组(可能是之前版本的遗留)
int f[U], f2[U]; // 用于存储最长子序列的辅助数组
int I=1; // 输入计数器,从1开始
int main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin); // 文件输入重定向
freopen("c.out","w",stdout); // 文件输出重定向
#endif
// 读取输入数据
int x;
while(cin>>x) a[I]=x, I++; // 持续读取输入直到结束
I--; // 修正计数器(因为最后会多+1)
// 第一部分:计算最长不上升子序列长度
int ans=0; // 存储最长不上升子序列长度
for(int i=1;i<=I;i++) // 遍历输入序列
{
// 在f数组中查找第一个小于a[i]的位置
auto it=upper_bound(f+1,f+ans+1,a[i],greater<int>());
int last=it-f-1; // 计算当前位置的前驱长度
int x=last+1; // 当前元素能构成的新长度
ans=max(ans,x); // 更新最大长度
f[x]=a[i]; // 更新f数组
}
cout<<ans<<endl; // 输出最长不上升子序列长度
// 第二部分:计算最长上升子序列长度
int ans2=0; // 存储最长上升子序列长度
for(int i=1;i<=I;i++) // 遍历输入序列
{
// 在f2数组中查找第一个大于等于a[i]的位置
auto it=lower_bound(f2+1,f2+ans2+1,a[i]);
int x=it-f2; // 计算当前元素能插入的位置
ans2=max(ans2,x); // 更新最大长度
f2[x]=a[i]; // 更新f2数组
}
cout<<ans2<<endl; // 输出最长上升子序列长度
return 0;
}

浙公网安备 33010602011771号