题目:[NOIP1999]拦截导弹
问题编号:217
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入数据为两行,
第一行为导弹的数目N(n<=1000)
第二行导弹依次飞来的高度,所有高度值均为不大于30000的正整数。
输出只有一行是这套系统最多能拦截的导弹数和要拦截所有导弹最少要配备这种导弹拦截系统的套数。两个数据之间用一个空格隔开.
题意:
见上。
思路:
首先说容易想到的o(n^2)的算法。dp[i]表示以第i个导弹结尾的最长非递增子序列。
那么dp[i]=max(dp[j]+1).bom[j]>=bom[i]。j<i。bom[i]表示第i个导弹的高度。
然后遍历dp[]找最大值即可。
对于拦截的所有导弹的最少系统数。贪心思想。能拦截就用最小射程的系统拦截。不能拦就新开一个系统。
考虑到导弹的射程满足单调性。可以二分查找。详细见代码:
#include <iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[1010],hei[1010],bom[1010],cnt; void addf(int h) { int l,r,mid,ans=-1; l=0,r=cnt-1; while(l<=r) { mid=(l+r)>>1; if(hei[mid]>=h) ans=mid,r=mid-1; else l=mid+1; } if(ans==-1) hei[cnt++]=h; else hei[ans]=h; } int main() { int i,j,n,ans; while(~scanf("%d",&n)) { cnt=0; for(i=1;i<=n;i++) { scanf("%d",bom+i); addf(bom[i]); } dp[1]=ans=1; for(i=2;i<=n;i++) { dp[i]=1; for(j=1;j<i;j++) if(bom[j]>=bom[i]) dp[i]=max(dp[i],dp[j]+1); } for(i=2;i<=n;i++) ans=max(ans,dp[i]); printf("%d %d\n",ans,cnt); } return 0; }
对于o(n*log(n))的算法。最少系统数仍然二分。对于一个系统可以拦截最多导弹数要换一种思路。
我们用dp[i]表示拦截导弹数为i系统的最大射程。对于bom[i]我们找到最大的j使得dp[j]>=bom[i]。那么
dp[j+1]=max(dp[j+1],bom[i])。感觉有点贪心的思想。同样的长度使结尾最大以给后面留更多的选择余地。
详细见代码:
#include <iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[1010],hei[1010],bom[1010],cnt,len; void addf(int h) { int l,r,mid,ans=-1; l=0,r=cnt-1; while(l<=r) { mid=(l+r)>>1; if(hei[mid]>=h) ans=mid,r=mid-1; else l=mid+1; } if(ans==-1) hei[cnt++]=h; else hei[ans]=h; } void binf(int x) { int l,r,mid,ans=-1; l=1,r=len; while(l<=r) { mid=(l+r)>>1; if(dp[mid]>=x) { ans=mid; l=mid+1; } else r=mid-1; } if(ans>0) { dp[ans+1]=max(dp[ans+1],x); if(ans+1>len) len++; } else if(r<l) dp[1]=x; } int main() { int i,n; while(~scanf("%d",&n)) { cnt=0; for(i=1;i<=n;i++) { scanf("%d",bom+i); addf(bom[i]); } dp[1]=bom[1]; len=1; for(i=2;i<=n;i++) binf(bom[i]); printf("%d %d\n",len,cnt); } return 0; }