P1020 导弹拦截 O(nlogn)做法

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是 (le 50000)的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

1行,若干个整数(个数 (le 100000)

输出格式

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

第一问就是求一个最长不升子序列。
第二问根据Dilworth定理就是求一个最长上升子序列

每一个任务我们都可以(n^2)地完成。
我们在这基础上可以优化一下,以第一个子问题为例,用(f[i])表示(dp)值为(i)的最大的高度。然后在我们更新(dp[x])时,只需要找到最大的(f[i]),这时(dp[x] = i + 1)
为什么呢?考虑如果有两个导弹下标分别为a,b(都在i前),如果a不仅比b高,而且(dp[a] > dp[b]),那么相对a来说,b对于(dp[i])是不会有贡献的。
并且,通过观察我们发现(f)数组的值是单调的,所以我们可以二分找到我们需要的(f[i])

代码如下:

#include<bits/stdc++.h>
using namespace std;

int dp1[100010],dp2[100010],f[100010];
int a[100010];
int main(){
	int tot = 0;
	while(scanf("%d",&a[++tot]) == 1);
		f[0] = max(f[0],a[tot]);
	--tot;
	for(int i = 1; i <= tot; ++i){
		int l = 0,r = tot;
		while(l < r){
			int mid = (l + r + 1) >> 1;
			if(f[mid] >= a[i]) l = mid;
			else r = mid - 1;
		}
		dp1[i] = l + 1;
		f[dp1[i]] = max(f[dp1[i]],a[i]);
	}
	memset(f,0x3f,sizeof(f));
	for(int i = 1; i <= tot; ++i){
		int l = 0,r = tot;
		while(l < r){
			int mid = (l + r + 1) >> 1;
			if(f[mid] < a[i]) l = mid;
			else r = mid - 1;
		}
		dp2[i] = l + 1;
		f[dp2[i]] = min(f[dp2[i]],a[i]);	
	}
	int ans1 = 0,ans2 = 0;
	for(int i = 1; i <= tot; ++i){
		ans1 = max(ans1,dp1[i]);
		ans2 = max(ans2,dp2[i]);
	}
	cout << ans1 << endl << ans2 << endl;
	return 0;
}
原文地址:https://www.cnblogs.com/FoxC/p/11351110.html