算法学习笔记——动态规划.最长上升/下降子序列 2024.5.24

Lanqiao OJ 773

这道题是一道动态规划的题目,主要考察最长上升/下降子序列,难度中等,但是对思维的考察比较重要,特别是如何解决其第二问

题目描述

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

输入导弹依次飞来的高度(雷达给出的高度数据是不大于 30000 的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入描述

输入 1 行,若干个整数(个数 ≤1e5)

输出描述

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

输入输出样例

示例

输入

389 207 155 300 299 170 158 65

输出

6
2

解题思路: 

1. 整体思路分析

第一问:由于导弹拦截系统打出的导弹一发比一发低,所以我们可以得出导弹的高度一定是单调递降的。求最多能拦截多少发炮弹就是求导弹高度的最长下降子序列的长度

第二问:用了一些贪心的思想

我们首先按照正常思维来考虑一下。下图是分别以导弹的高度和时间为x轴和y轴的对照表,当我们有一套导弹拦截系统时,可以打掉一串高度为最长下降子序列的导弹,如图黑线所示

打掉这些导弹后,还有导弹,那我们再用一套导弹拦截系统,再打掉一串最长下降子序列

 还剩一个导弹,那我们就再用一套,直到打掉所有导弹

我们将这三条最长下降子序列和整体的最长上升子序列画出来,可以发现,每条最长下降子序列曲线正好与整体最长上升子序列有一个交点,所以最长上升子序列的长度就是我们需要的导弹拦截系统的套数。

解释的更具体一些,如果我们先画出最长上升子序列,再使用三套导弹拦截系统,那么我们可以发现, 每使用一次导弹拦截系统清除一串最长下降子序列,必定会且仅会清除最长上升子序列上的一个点(这是因为两者的单调性相反,只可能有一个点相交),于是每使用一套系统就会清除最长上升子序列上的一个点,直到清除完。所以可以得出结论:最长上升子序列的长度(有多少个点)就是所需的导弹拦截系统的套数。

2. 代码实现

首先是输入,这题的输入比较烦人,没直接给你n,所以我们用cin >> h[++n]进行输入循环,但是下一个没有任何输入,那么输入流cin就会一直等待,所以我们要用cin.get()检测下一个是不是换行,从而终止循环。

注:cin.get()函数可以用来从输入流中读取一个字符,返回该字符并将读取位置向后移动一位。

int n = 0;
  while(cin >> h[++n])
  {
    if(cin.get() == '\n') break;
  }

然后就是动态规划,开两个数组dp1[],dp2[]来分别储存下降子序列和上升子序列

//动态规划,求最长下降子序列和最长上升子序列
  for(int i = 1; i <= n; i++)
  {
    dp1[i] = 1;
    dp2[i] = 1;
    for(int j = 1; j < i; j++)
    {
      if(h[j] > h[i]) dp1[i] = max(dp1[i], dp1[j] + 1);//下降子序列
      if(h[j] < h[i]) dp2[i] = max(dp2[i], dp2[j] + 1);//上升子序列
    }
  }

最后求出最大值即可

//分别求最长下降子序列和最长上升子序列的长度
  int x = *max_element(dp1+1, dp1+n+1);
  int y = *max_element(dp2+1, dp2+n+1);

以下是完整代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 1;
int h[N], dp1[N],dp2[N];
int main()
{
  //输入
  int n = 0;
  while(cin >> h[++n])
  {
    if(cin.get() == '\n') break;
  }
  //动态规划,求最长下降子序列和最长上升子序列
  for(int i = 1; i <= n; i++)
  {
    dp1[i] = 1;
    dp2[i] = 1;
    for(int j = 1; j < i; j++)
    {
      if(h[j] > h[i]) dp1[i] = max(dp1[i], dp1[j] + 1);//下降子序列
      if(h[j] < h[i]) dp2[i] = max(dp2[i], dp2[j] + 1);//上升子序列
    }
  }
  //分别求最长下降子序列和最长上升子序列的长度
  int x = *max_element(dp1+1, dp1+n+1);
  int y = *max_element(dp2+1, dp2+n+1);
  cout << x << '\n' << y << '\n';
  return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值