洛谷P1020 [NOIP1999 提高组] 导弹拦截 && LIS与LDS
传送门:P1020 [NOIP1999 提高组] 导弹拦截
愛にできることわまだあるか
题目大意:
一个拦截导弹的系统,每次只能拦截高度不超过上一个的导弹
给出所有导弹的高度,
求出:
- 一个该系统最多能拦截的导弹数量;
- 要拦截所有导弹最少需要的该系统的数量。
思路:
第一问:
一眼就是 最长单调不上升子序列
复习一下:
- 最长上升子序列:参考B3637 最长上升子序列
dp[i] 表示以 i 结尾的最长上升子序列的长度,则 if ( a[ i ] > a[ j ] ) dp[ i ] = max( dp[ i ] , dp[ j ] + 1 )
最后 还要求一个最大值.
for(int i=1;i<=n;i++) { dp[i]=1; for(int j=1;j<i;j++) { if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1); } maxn=max(maxn,dp[i]); }
- 最长不上升子序列:
则有:a[ i ] <= a[ j ]。 ( i > j )
其实就是条件判断的时候多加一个 =
for(int i=1;i<=n;i++) { dp[i]=1; for(int j=1;j<i;j++) { if(a[j]>=a[i])dp[i]=max(dp[i],dp[j]+1); } maxn=max(maxn,dp[i]); }
但是这样会喜提 100 分 qwq( 满分200 )
因为这个复杂度是 O( n^2 ) 的
考虑优化:
-
我们发现,在 DP 的过程中,我们要反复寻找满足条件的 j,并要找到最大的 dp[ j ] + 1,其实我们只需要找到 所有 dp[ j ] 中的最大值就好了,可以使用线段树或树状数组
-
二分查找
STL 的二分查找函数 lower_bound:在一个有序 ( 默认升序 )序列中进行二分查找,返回指向第一个 大于等于 x 的元素的位置的迭代器。如果不存在这样的元素,则返回尾迭代器lower_bound(v.begin(),v.end(),x)。 upper_bound:在一个有序 ( 默认升序 )序列中进行二分查找,返回指向第一个 大于 x 的元素的位置的迭代器。如果不存在这样的元素,则返回尾迭代器。upper_bound(v.begin(),v.end(),x)。
但是这些我都不用(觉得好麻烦qwq
先放张图:拿样例解释一下最长不下降子序列
最长上升子序列的方法类似
这样就能保证在 O( nlogn ) 的时限内求解了
这个大佬写的挺好
这个大佬写的也挺好
第二问:
-
简单贪心
从左到右依次枚举每个导弹。假设现在有若干个导弹拦截系统可以拦截它,那么我们肯定选择这些系统当中上一个拦截导弹位置最低的那一个。
如果不存在任何一个系统可以拦截它,那我们只能新加一个系统了。
时间复杂度 O(nlogn),可以通过此题。 -
通过贪心,我们会发现一个规律:
求要拦截所有导弹最少需要的该系统的数量,其实就是要求最少的不上升子序列的个数
贪心得出来的结果等价于计算最长上升子序列,其实这是Dilworth 定理这是啥???看不懂啊。。
可以看一下这个大佬写的
总之就是:最少的不上升子序列的个数就是最长上升子序列的长度。
代码:
其实就是求一下 最长单调不上升子序列 和 最长上升子序列的长度 了
(这里先用二分查找,因为线段树还没明白,明白了会补)
#include<bits/stdc++.h> using namespace std; int x,n; int a[100100]; int d[100100],p[100100]; int cnt; int main() { freopen("a.in","r",stdin); while(cin>>x) n++,a[n]=x; d[0]=0x3f3f3f; for(int i=1;i<=n;i++)//求最长不下降子序列 { if(a[i]<=d[cnt]) d[++cnt]=a[i]; else{ int left=1,right=cnt; while(left<right)//查找第一个 < a[i] 的元素的位置 (因为等于的时候不需要替换) { int mid=left+right>>1; if(d[mid]<a[i])right=mid; else left=mid+1; } d[left]=a[i]; } } cout<<cnt<<endl;//这时的最长不下降子序列长度就是 cnt cnt=0; p[0]=-1; for(int i=1;i<=n;i++) //求 最长上升子序列的长度 { if(a[i]>p[cnt])p[++cnt]=a[i];// 查找第一个 >= a[i] 的元素的位置 else{ int left=1,right=cnt; while(left<right) { int mid=left+right>>1; if(p[mid]>=a[i]) right=mid; else left=mid+1; } p[left]=a[i]; } } cout<<cnt<<endl; return 0; }
后记:
好麻烦的一道好题
又做了一下午
主要优化的部分挺难理解的
一道思路差不多的题目:P1091 [NOIP2004 提高组] 合唱队形
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战