洛谷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 提高组] 合唱队形