洛谷P1020 [NOIP1999 提高组] 导弹拦截 && LIS与LDS

传送门:P1020 [NOIP1999 提高组] 导弹拦截

愛にできることわまだあるか

题目大意:

一个拦截导弹的系统,每次只能拦截高度不超过上一个的导弹
给出所有导弹的高度,
求出:

  1. 一个该系统最多能拦截的导弹数量;
  2. 要拦截所有导弹最少需要的该系统的数量。

思路:

第一问:

一眼就是 最长单调不上升子序列
复习一下:

  • 最长上升子序列:参考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 提高组] 合唱队形

posted @ 2024-08-18 22:14  lazy_ZJY  阅读(10)  评论(0编辑  收藏  举报