攔截導彈(dp+二分+數學)
攔截導彈(dp+二分+數學)
連結:https://ac.nowcoder.com/acm/problem/16810
來源:牛客網
某國為了防禦敵國的導彈襲擊,發展出一種導彈攔截系統。但是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈能夠到達任意的高度,但是以後每一發炮彈都不能高於前一發的高度。某天,雷達捕捉到敵國的導彈來襲。由於該系統還在試用階段,所以只有一套系統,因此有可能不能攔截所有的導彈。
輸入導彈依次飛來的高度(雷達給出的高度資料是不大於30000的正整數),計算這套系統最多能攔截多少導彈,如果要攔截所有導彈最少要配備多少套這種導彈攔截系統。
輸入描述:
1行,若干個整數(個數≤100000)
輸出描述:
2行,每行一個整數,第一個數字表示這套系統最多能攔截多少導彈,第二個數字表示如果要攔截所有導彈最少要配備多少套這種導彈攔截系統。
示例1
輸入
389 207 155 300 299 170 158 65
389 207 155 300 299 170 158 65
輸出
6 2
6
2
題意分析:
就是要求輸出最大不上升子序列,和最少劃分的不上升子序列個數。
由Dilworth定理可知,一個序列的最小劃分的不上升子序列個數等於該序列的最大上升子序列長度。
這裡提供兩種演算法:
O演算法:假設序列為a,設dp[i]為以a[i]為結尾的最大不上升序列的長度,則我們可以通過j等於0到i-1個dp[j],由dp[i]=max(dp[i],dp[j]+1));可以在
求最大上升子序列長度同理。
程式碼如下:
#include<bits/stdc++.h> using namespace std; const int MAX=13; int a[MAX],dp[MAX]; int main() { int n=1; while(~scanf("%d",&a[n]))n++; n--; dp[1]=1; int ans=0; for(int i=2;i<=n;i++) { dp[i]=1; for(int j=1;j<i;j++) { if(a[i]<=a[j]) dp[i]=max(dp[i],dp[j]+1); } //cout<<dp[i]<<endl; ans=max(ans,dp[i]); } cout<<ans<<endl; memset(dp,0,sizeof(dp)); dp[1]=1; ans=0; for(int i=2;i<=n;i++) { dp[i]=1; for(int j=1;j<i;j++) { if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1); } //cout<<dp[i]<<endl; ans=max(ans,dp[i]); } cout<<ans<<endl; }
O(nlogn)演算法:假設dp[ans]為長度為ans,dp[ans]為最優尾的值。對於最大不上升子序列,dp[ans]越大越好。
易知,由於dp[ans]中必定是經過dp[ans-1]成為最優解的,所有dp陣列是個不遞減陣列,所以當我們得到一個a[i]時,若a[i]大於或等於dp[ans],則dp[++ans]=a[i];
若a[i]小於dp[ans],我們可以通過二分法去讓a[i]更新前面的dp陣列,使其第一個大於a[i]的最優尾數值改變為a[i]。
我們便能在nlogn時間內解決這道題。
程式碼如下:
#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
int a[MAX],dp[MAX];
int main()
{
int n=1;
while(~scanf("%d",&a[n]))n++;
n--;
dp[1]=a[1];
int ans=1;
for(int i=2;i<=n;i++)
{
if(dp[ans]>=a[i])dp[++ans]=a[i];
else
{
int m=upper_bound(dp+1,dp+1+ans,a[i],greater<int>())-dp;
dp[m]=a[i];
}
}
cout<<ans<<endl;
ans=1;
dp[1]=a[1];
for(int i=2;i<=n;i++)
{
if(dp[ans]<a[i])dp[++ans]=a[i];
else
{
int m=lower_bound(dp+1,dp+1+ans,a[i])-dp;
dp[m]=a[i];
}
}
cout<<ans<<endl;
}