这一题分为两个问题,一个是一台最多能射下多少枚导弹,一个是需要多少太拦截设备,第一个问很容易知道就是求最长不递增子序列,第二问就是求最长上升子序列,我们稍微证明一下第二问,因为导弹拦截只能拦截越来越低的,所以遇到比当前高的时候就要重新加一台,最长上升子序列的长度就是至少需要的台数,子序列中每一颗导弹都不能用同一拦截器拦截。
两种做法:一种是N方的做法,我们可以发现一台设备已经拦截了K枚导弹,能否拦截第K+1枚,就要比较第K+1枚导弹和最后一枚导弹的高度,我们只要dp[i],表示如果i作为最后一颗拦截的导弹最长有多少,它可以由dp[j]决定,j<i而且a[j]>=a[i],实际含义就是在i前面找比第i颗导弹高的导弹,然后任意一个比第i颗导弹高的导弹后面,所以存在下面的状态转移方程:
for(int i=1;i<cnt;i++) for(int j=1;j<i;j++) if(a[j]>=a[i]) dp[i]=max(dp[j]+1,dp[i]);
第二问求法类似,我们同样每种情况只考虑结尾:代码如下
#include<iostream> using namespace std; int a[100001],dp[100001],dp1[100001]; int main() { int cnt=1; while(cin>>a[cnt]) cnt++; for(int i=1;i<cnt;i++) dp1[i]=1,dp[i]=1; for(int i=1;i<cnt;i++) for(int j=1;j<i;j++) { if(a[j]>=a[i]) dp[i]=max(dp[j]+1,dp[i]); else dp1[i]=max(dp1[j]+1,dp1[i]); } int ans=0,ans2=0; for(int i=1;i<=cnt;i++) { ans=max(ans,dp[i]); ans2=max(ans2,dp1[i]); } cout<<ans<<" "<<ans2; }
第二种做法叫做在线处理,我们dp[i]代表第i颗导弹能够拦截的最大高度,维护一个单调数组,当进来的那颗导弹比最后一刻导弹低时加入队列,当比他高时,我们看它最多前面能继承的多少颗导弹,在队列中二分找到第一个比他小的数字,替代掉;
1 #include<iostream> 2 using namespace std; 3 int a[100001],dp[100001],dp1[100001]; 4 //在线处理,我们考虑每个位置打那个更好,维护一个单调子序列 5 int main() 6 { 7 int cnt=0,p1=0,p2=0; 8 while(cin>>a[cnt]) cnt++; 9 dp[0]=a[0],dp1[0]=a[0]; 10 for(int i=1;i<cnt;i++) 11 { 12 if(dp[p1]>=a[i]) dp[++p1]=a[i]; 13 else 14 { 15 int l=0,r=p1; 16 while(l<r)//在里面找第一个小于它的数 17 { 18 int mid=(l+r)>>1; 19 if(dp[mid]<a[i]) r=mid; 20 else l=mid+1; 21 } 22 dp[l]=a[i]; 23 } 24 if(dp1[p2]<a[i]) dp1[++p2]=a[i]; 25 else 26 { 27 int l=0,r=p2;//在这里面找第一个大于等于它的数 28 while(l<r) 29 { 30 int mid=(l+r)>>1; 31 if(dp1[mid]>=a[i]) r=mid; 32 else l=mid+1; 33 } 34 dp1[l]=a[i]; 35 } 36 } 37 cout<<p1+1<<" "<<p2+1; 38 }