题解 P1020 【[NOIP1999 普及组] 导弹拦截】
第一问
将拦截的导弹的高度提出来成为原高度序列的一个子序列,根据题意这个子序列中的元素是单调不增的(即后一项总是不大于前一项),我们称为单调不升子序列。本问所求能拦截到的最多的导弹,即求最长的单调不升子序列。
考虑记
- 第
i 个数是子序列的第一项。则\mathit{dp}_i\gets 1 。 - 第
i 个数不是子序列的第一项。选择的第i 个数之前选择了第j 个数。根据题意,第j 个数的值h(j) 应当小于第i 个数的值h(i) 。枚举这样的j ,可以得到状态转移方程:
综合这两种情况,得到最终的状态转移方程:
值得注意的是,第
直接枚举进行状态转移,时间复杂度显然是
记
- 随
i 增大,f_i 单调不增。即f_i\ge f_{i+1} 。
考虑使用反证法。假设存在
因此
现在考虑以
根据刚刚得出的结论,
绿色区域表示合法的
假设二分区域为
当
时间复杂度
第二问
考虑贪心。
从左到右依次枚举每个导弹。假设现在有若干个导弹拦截系统可以拦截它,那么我们肯定选择这些系统当中位置最低的那一个。如果不存在任何一个导弹拦截系统可以拦截它,那我们只能新加一个系统了。
假设枚举到第
时间复杂度
参考代码
#include<bits/stdc++.h>
#define up(l,r,i) for(int i=l,END##i=r;i<=END##i;++i)
#define dn(r,l,i) for(int i=r,END##i=l;i>=END##i;--i)
using namespace std;
typedef long long i64;
const int INF =2147483647;
const int MAXN=1e5+3;
int n,t,H[MAXN],F[MAXN];
int main(){
while(~scanf("%d",&H[++n])); --n;
t=0,memset(F,0,sizeof(F)),F[0]=INF;
up(1,n,i){
int l=0,r=t+1; while(r-l>1){
int m=l+(r-l)/2;
if(F[m]>=H[i]) l=m; else r=m;
}
int x=l+1; // dp[i]
if(x>t) t=x; F[x]=H[i];
}
printf("%d\n",t);
t=0,memset(F,0,sizeof(F)),F[0]=0;
up(1,n,i){
int l=0,r=t+1; while(r-l>1){
int m=l+(r-l)/2;
if(F[m]<H[i]) l=m; else r=m;
}
int x=l+1;
if(x>t) t=x; F[x]=H[i];
}
printf("%d\n",t);
return 0;
}
观察第二问的代码,与第一问进行比较,可以发现这段代码等价于计算最长上升子序列(严格上升,即后一项大于前一项)。这其实是