题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
我这里讲的是nlogn的做法。
首先看第一问,第一问就是要求一个最长不下降子序列,这个O(n^2)的做法是很容易的。
我这里介绍的nlogn的方法是用线段树的。我们发现高度是不超过50000的,所以我们可以把线段树的每一个下标i的含义变为目前加进来的高度为i的最长不下降子序列的长度是多少。然后我们需要做的是从后往前扫,每到一个位置,查询当前线段树从1到当前高度a[i]的区间最大值,当前的dp[i]即为那个最大值再加1,然后用dp[i]更新线段树即可。
然后看第二问,据说有个关于偏序集的定理,但是我觉得不太好理解,于是我自己想了一种理解方式。
我们考虑如果需要多出一套系统,就意味着现在出现的这一个导弹的高度比上一个导弹高,这样就不能用一套系统拦截这两个导弹了,我们此时就需要新加一个系统。所以我们把第二问转化为了求最长上升子序列的长度。
下面是代码:
#include <bits/stdc++.h> using namespace std; int n,a[200001],dp[200001],res; struct node { int l,r,mx; }tr[205002]; void build(int rt,int l,int r) { tr[rt].l=l; tr[rt].r=r; if(l==r) { tr[rt].mx=0; return; } int mid=(l+r)>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); tr[rt].mx=0; } void add(int rt,int le,int ri,int x) { int l=tr[rt].l,r=tr[rt].r; if(l>ri||r<le) return; if(le<=l&&r<=ri) { tr[rt].mx=max(tr[rt].mx,x); return; } int mid=(l+r)>>1; if(le<=mid) add(rt<<1,le,ri,x); if(ri>mid) add(rt<<1|1,le,ri,x); tr[rt].mx=max(tr[rt<<1].mx,tr[rt<<1|1].mx); } int query(int rt,int le,int ri) { int l=tr[rt].l,r=tr[rt].r,ans=0; if(l>ri||r<le) return 0; if(le<=l&&r<=ri) return tr[rt].mx; int mid=(l+r)>>1; if(le<=mid) ans=query(rt<<1,le,ri); if(ri>mid) ans=max(ans,query(rt<<1|1,le,ri)); return ans; } int main() { while(~scanf("%d",&a[++n])); build(1,1,50000); for(int i=n;i>=1;i--) { dp[i]=query(1,1,a[i])+1; add(1,a[i],a[i],dp[i]); res=max(res,dp[i]); } printf("%d\n",res); memset(dp,0,sizeof(dp)); res=0; build(1,1,50000); for(int i=1;i<=n;i++) { dp[i]=query(1,1,a[i]-1)+1; add(1,a[i],a[i],dp[i]); res=max(res,dp[i]); } printf("%d\n",res); return 0; }