题目描述:

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

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

输入格式

共一行,输入导弹依次飞来的高度。

输出格式

第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

输入样例:

389 207 155 300 299 170 158 65

输出样例:

6
2

分析:

本题是练习动态规划,贪心以及二分的好题,下面我将分别用这三种方法求解本题。题目让我们求解两个数量,第一个是最长不上升子序列的长度,第二个是序列最少可以划分为几个不上升子序列。

方法一:动态规划

首先求解最长不上升子序列的长度,f[i]表示以a[i]为末尾的最长不上升子序列的长度,状态转移方程为f[i] = max(f[i],f[j] + 1),其中j < i,a[i] <= a[j]。第二个问题求拦截所有导弹需要配备多少个系统,稍微复杂些。用DP解决需要用到Dilworth定理。对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。此定理的对偶形式亦真,它断言:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目。应用到本题就是说把一个数列划分成最少的最长不升子序列的数目就等于这个数列的最长上升子序列的长度。故第二个问题就转化为了求LIS的长度,g[i] = max(g[i],g[j] + 1),i > j,a[i] > a[j]。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int f[N],a[N],b[N];
int main(){int n = 0;while(cin>>a[n])   n++;int res = 0;for(int i = 0;i < n;i++){f[i] = 1;for(int j = 0;j < i;j++){if(a[i] <= a[j])    f[i] = max(f[i],f[j]+1);}res = max(res,f[i]);}int res1 = 0;for(int i = 0;i < n;i++){b[i] = 1;for(int j = 0;j < i;j++){if(a[i] > a[j]) b[i] = max(b[i],b[j]+1);}res1 = max(res1,b[i]);}cout<<res<<endl<<res1<<endl;return 0;
}

方法二:贪心

在AcWing 896 最长上升子序列 II中我们介绍了LIS问题的贪心+二分解法,但是是强行把DP转化为贪心的解释,不容易理解,下面正式从纯贪心的角度来分析这类问题。第一个问题,求最长不上升子序列的长度,在各个长度的不上升子序列中,只有末字符会影响子序列的延展,因此,我们需要存储各个长度不上升子序列的末字符,而且为了后面的字符能够接上不上升序列,前面子序列的末字符越大越好,所以我们需要维持一个数组,始终存储着各个长度的不上升子序列末字符的最大者。举个例子:3 6 5 5 8 2。遍历下该序列,遍历到3时,发现一个长度为1的不上升子序列,因此f[0] = 3,然后遍历到6,6无法接到3后面且大于3,因此长度为1的不上升子序列的末字符替换为f[0] = 6,遍历到5,出现了长度为2的不上升子序列,于是f[1] = 5,接着遍历还是5,655也是不上升序列,故f[2] = 5,然后是8,没有比8大的,故f[0] = 8,之后是2,f[3] = 2.最后的不上升子序列是长度是4。因为我们始终将f数组中小于a[i]的最大者替换为a[i],故f数组总是非上升的。

第二个问题,求序列最少能划分为几个不上升子序列。我们可以维持每个子序列的末元素,然后在各个末元素中找到不小于a[i]的最小的末元素,将a[i]接到末尾。还是以3 6 5 5 8 2为例,第一个序列为3,g[0] = 3,然后是6无法接到3后面,于是第二个子序列末字符是g[1] = 6,然后5可以接到6后面,于是g[1] = 5,接着5还可以接到5后面,于是g[1] = 5,8大于所有末字符,新建第三个子序列f[2] = 8,2可以接到三个子序列任意一个后面,为了让末字符的较大着留到后面,于是把2接到3后面,得到g[0] = 3.最后求得最少可以划分为三个不上升子序列,分别是3 2,6 5 5,8.g数组始终存储着各个子序列的末字符,为什么遍历到2时要接到3后面而不是8后面,因为接到3后面,如果下一个是7,7可以接到8后面从而不用新建序列,但是把2接到8后面的话遍历到7就将必须新建一个不上升子序列了。

可见,求LIS问题用DP容易理解,求最少划分序列数问题用贪心更容易理解,因此如果不理解求最长不上升子序列的贪心解法,可以转化为其反链问题求数列能够划分为的下降子序列的最小个数,就容易理解问题一的贪心思想了。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int a[N],f[N],g[N];
int main(){int n = 0,res = 0,len = 0;while(cin>>a[n])    n++;for(int i = 0;i < n;i++){int j = 0;while(j < len && f[j] >= a[i])  j++;f[j] = a[i];len = max(len,j + 1);}res = len,len = 0;for(int i = 0;i < n;i++){int j = 0;while(j < len && g[j] < a[i])   j++;g[j] = a[i];len = max(len,j + 1);}cout<<res<<endl<<len<<endl;return 0;
}

方法三:贪心 + 二分

在贪心解法中,需要求f数组中不小于a[i]的最小的数,有f数组是单调非增的,因此完全可以用二分去查找。查找g数组中元素时也可以用二分去查找。

首先求最长不上升子序列长度,需要在f数组(单调非增)中找到最小的不小于a[i]的数,然后用a[i]替换掉其后一个元素。比如6 5 5 3,在其中查找5时,不小于5的最后一个数是5,然后用5替换掉5后面的数3得到6 5 5 5;如果是在6 5 5 3中查找2,则等同于在3的后面插入2。这也是为什么我们不直接查找小于a[i]的第一个位置的原因,因为有可能f数组中所有的数都大于a[i],我们查找不小于a[i]的最大的数,其后面不论有没有数都可以替换为a[i]。这里的二分查找是找到了就替换,找不到就新增,所以我们将目标设定为在单调非增序列中查找不小于a[i]的最小数位置。下面是如何进行二分,f[mid] >= a[i]时,待查找的位置一定不在mid左边,所以l = mid,f[mid] < a[i],则待查找位置一定在mid左边,故r = mid - 1。我们最终的目标是l = r 且他们的指向的元素都是不小于a[i]的,所以遇见小于a[i]的,r需要指向mid - 1。另外,避免二分死循环的关键在于l和r指针是否相对于mid移动了。当r = l + 1时,如果mid = l + r >> 1,mid = l,此时若满足l = mid的条件将永远死循环,所以只有l = mid + 1时才可避免死循环。当r = l + 1时,如果mid = l + r + 1 >> 1,mid = r,此时若满足r = mid - 1的条件,r会回退一格,使l与r相遇,循环终止。所以我们这里mid取l + r + 1 >> 1。这是二分的一条特别重要的经验,即以l == r作为二分循环的终止条件时,如果mid = l + r >> 1,即mid取(l + r) / 2下取整,此时l必须转移到mid + 1,否则会造成死循环;如果mid = l + r + 1 >> 1,即mid取(l + r) / 2上取整,此时r必须转移到mid - 1,否则会造成死循环。很多人不知道二分出现死循环的原因,只要记住这一条便不会出现死循环。

正如上面的步骤,二分程序设计的思路需要首先明确查找的目标位置,设计出循环退出时l与r的位置,设计出mid的取值以及满足何条件时,l与r是否要相对于mid移动。下面用这个步骤来解决第二个问题,求数列最少能够被划分为多少个不上升子序列。只有出现大于已存在所有子序列末尾的元素才会新建子序列,所以g数组是严格单调递增的(之前的f是单调非增的)。我们要在g中找到不小于a[i]的最小值,等价的转化为找到小于a[i]的最大值,后面一个元素即是所求位置,即最终l指向小于a[i]的最大值。g[mid] < a[i]时,待查找位置一定不在mid左边,故l = mid;g[dmid] >= a[i]时,待查找位置一定在mid左边,故r = mid - 1.r的位置相对于mid发生了位移,因此,mid = l + r + 1 >> 1。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int a[N],f[N],g[N];
int main(){int n = 0,res = 0;while(cin>>a[n])    n++;int len = 0,l,r;for(int i = 0;i < n;i++){l = 0,r = len;while(l < r){int mid = l + r + 1>> 1;if(f[mid] >= a[i])  l = mid;else    r = mid - 1;}f[l + 1] = a[i];len = max(len,l + 1);}res = len,len = 0;for(int i = 0;i < n;i++){l = 0,r = len;while(l < r){int mid = l + r + 1>> 1;if(g[mid] < a[i])  l = mid;else    r = mid - 1;}g[l + 1] = a[i];len = max(len,l + 1);}cout<<res<<endl<<len<<endl;return 0;
}

AcWing 1010 拦截导弹相关推荐

  1. 最长上升子序列模型 AcWing 1010. 拦截导弹

    最长上升子序列模型 AcWing 1010. 拦截导弹 原题链接 AcWing 1010. 拦截导弹 算法标签 DP 线性DP 最长上升子序列 思路 摘自该题解 代码 #include<bits ...

  2. AcWing 1010. 拦截导弹

    好久没写啦, 今天学acwing打算分享一下拦截导弹这样一道lis题目, 方法就是dp和贪心 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统. 但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能 ...

  3. 拦截导弹(dp【最长上升子序列模型】 + 贪心)

    AC Wing 1010 拦截导弹 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统. 但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度 ...

  4. 信息学奥赛一本通(1322:【例6.4】拦截导弹问题(Noip1999))

    1322:[例6.4]拦截导弹问题(Noip1999) 时间限制: 1000 ms         内存限制: 65536 KB 提交数: 14462     通过数: 5606 [题目描述] 某国为 ...

  5. 信息学奥赛第十节 —— 贪心算法(渡河问题POJ 1700 Crossing River + 拦截导弹的系统数量求解)

    复习概念 贪心算法又叫贪婪算法,是指在对问题求解时,总是做出在当前看来是最好的选择.也就是说,贪心算法不从整体最优上加以考虑,它所做出的是在某种意义上的局部最优解. 无后效性:贪心算法不是对所有问题都 ...

  6. #547. 拦截导弹(missile)

    题目链接: 登录 - 沐枫OJ 我的思路: 把题目分两部分,第一部分相当于求序列的最长下降子序列,第二部分则需要运用贪心的思想.下面分开详细讲解. #include<iostream> # ...

  7. BZOJ 2244: [SDOI2011]拦截导弹 DP+CDQ分治

    2244: [SDOI2011]拦截导弹 Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度.并且能够拦截 ...

  8. BZOJ - 2244 拦截导弹 (dp,CDQ分治+树状数组优化)

    BZOJ - 2244 拦截导弹 (dp,CDQ分治+树状数组优化) 1 #include<algorithm> 2 #include<iostream> 3 #include ...

  9. BZOJ2244 [SDOI2011]拦截导弹 【cdq分治 + 树状数组】

    题目 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度.并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其 ...

最新文章

  1. Django基础知识
  2. php和派森,安装多版本Python,一个神器足矣
  3. NFS Volume Provider(Part II) - 每天5分钟玩转 OpenStack(63)
  4. WEB前端学习笔记01利用纯CSS书写二级水平导航菜单
  5. Qt之QMetaObject::invokeMethod()使用简介
  6. 1005打印任务取消不了 hp_惠普HP M1136 MFP多功能打印机 一年半使用感受(学生打印机)...
  7. WINDOWS操作系统32位(x86)和64位(x64)的区别
  8. android实现下载的核心代码
  9. 【GPS】 根据GPS坐标求取两点间距离算法
  10. 二级c语言考试怎么调试程序,计算机二级C语言考试具体内容及分值
  11. pyhanlp常用功能简单总结
  12. typort快捷键+常用表情+画图
  13. nfs挂载文件系统时VFS: Unable to mount root fs on unknown-block的问题解决
  14. 2019年第十届蓝桥杯c/c++B组国赛决赛真题题目
  15. RPG Maker MV 图块冲突解决、素材管理
  16. CUT&RUN——检测蛋白-DNA相互作用的强大通用技术
  17. 第十六章 - 垃圾回收相关概念
  18. js 计算当前时间和和一段时候后的工作日天数,排除周末和法定假日
  19. 计算机主机内部的除尘课件,计算机主机内部除尘装置
  20. 文献分享 基于ECG和ECG呼吸信号特征的阻塞性和限制性肺病自动检测

热门文章

  1. springboot profiles多环境打包
  2. android-smart-image-view源码分析
  3. 管理1.0阶段到4.0阶段分别是什么
  4. Android App支付:支付宝SDK接入详细指南(附官方支付demo)
  5. 激光干涉仪如何检测三坐标测量机垂直度
  6. ARCGIS展点(XY坐标)及矢量转CAD坐标显示的问题
  7. python把数据按照时间排序,python中如何根据日期对数据进行排序
  8. 【C语言】图片文件 预处理
  9. 组态王6.53破解下载
  10. 河北工业大学英语笔译(专硕)考研上岸经验分享