1. 程式人生 > >LuoGuP1020導彈攔截【單調棧+二分深刻理解】【做題報告】

LuoGuP1020導彈攔截【單調棧+二分深刻理解】【做題報告】

這是一道水題,但是對我的二分有很大啟發,也可以n2DP但是顯然單調棧nlogn更佳

題目描述

某國為了防禦敵國的導彈襲擊,發展出一種導彈攔截系統。但是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈能夠到達任意的高度,但是以後每一發炮彈都不能高於前一發的高度。某天,雷達捕捉到敵國的導彈來襲。由於該系統還在試用階段,所以只有一套系統,因此有可能不能攔截所有的導彈。

輸入導彈依次飛來的高度(雷達給出的高度資料是\le 5000050000的正整數),計算這套系統最多能攔截多少導彈,如果要攔截所有導彈最少要配備多少套這種導彈攔截系統。

輸入輸出格式

輸入格式:

 

11行,若干個整數(個數\le 100000100000)

 

輸出格式:

 

22行,每行一個整數,第一個數字表示這套系統最多能攔截多少導彈,第二個數字表示如果要攔截所有導彈最少要配備多少套這種導彈攔截系統。

 

輸入輸出樣例

輸入樣例#1: 複製
389 207 155 300 299 170 158 65
輸出樣例#1: 複製
6
2

說明

為了讓大家更好地測試n方演算法,本題開啟spj,n方100分,nlogn200分

每點兩問,按問給分

這道題就是求最長不上升和上升子序列;

這是為什麼呢,第一問很好理解,第二問為為什麼求最長上升就行了呢??

這個可以腦補一下,相當於是一個個的上升序列,然後他們的隊頭構成了最後的最長上升

然後我就想,為什麼不能在每一個隊裡選出來更多的陣列成更長的答案?

這是不成立的,因為這個佇列是單調遞減,只能一個隊裡選一個(因為如果選兩個就不是上升了),所以一個隊裡選一個,最後答案還是一樣的,並不影響

然後呢,n2是白給的(思路很好懂,暴力貪心)

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 int cnt;
 6 int
a[100011]; 7 int dp[100011]; 8 int main() 9 { 10 // freopen("testdata.in","r",stdin); 11 while(scanf("%d",&a[++cnt])!=EOF)continue;cnt--; 12 dp[1]=1; 13 for(int i=2;i<=cnt;i++) 14 { 15 int tmp=0; 16 for(int j=1;j<=i-1;j++) 17 { 18 if(a[j]>=a[i])tmp=max(tmp,dp[j]); 19 } 20 dp[i]=max(dp[i],tmp+1); 21 } 22 int ans=-1; 23 for(int i=1;i<=cnt;i++)ans=max(ans,dp[i]); 24 printf("%d\n",ans); 25 memset(dp,0,sizeof(dp)); 26 dp[1]=1; 27 for(int i=2;i<=cnt;i++) 28 { 29 int tmp=0; 30 for(int j=1;j<=i-1;j++) 31 { 32 if(a[j]<a[i])tmp=max(tmp,dp[j]); 33 } 34 dp[i]=max(dp[i],tmp+1); 35 } 36 ans=-1; 37 for(int i=1;i<=cnt;i++)ans=max(ans,dp[i]); 38 printf("%d\n",ans); 39 return 0; 40 } 41 /*1 2 3 4 5*/ 42 /* 43 389 207 155 300 299 170 158 65 44 */

 

然後便是單調棧的寫法(一開始以為是單調佇列,但是單調佇列是維護需要出隊的,用來快取陣列,優化時間複雜度,顯然這道題並不用)

單調棧的思路就是維護一個單調的棧,每次尋找最底層比它大(或小)的位置將當前元素插入,最後棧內有多少個元素就是答案

那麼插入的過程呢,對,用二分來維護,這樣才能保證是每次log

那麼我們來看一下這個二分

for(int i=2;i<=cnt;i++)
    {
        int l=1,r=top,mid,tt=top+1;
        while(l<=r)
        {
            mid=(l+r)/2;
            if(stk[mid]>=a[i])l=mid+1;
            else r=mid-1,tt=mid;
        }
        if(tt==top+1)top++;
        stk[tt]=a[i];
    }
for(int i=2;i<=cnt;i++)
    {
        int l=1,r=top,mid,tt=top+1;
        while(l<=r)
        {
            mid=(l+r)/2;
            if(stk[mid]<a[i])l=mid+1;
            else r=mid-1,tt=mid;
        }
        if(tt==top+1)top++;
        stk[tt]=a[i];
    }

 

這個tt=mid,也就是記錄ans的地方到底應該加在哪裡呢,之前是背模板,以為就是在r那裡,但是不是這樣的,

應該是哪個符合你想要的在哪裡記錄ans,也就是有一個滿足就記錄一下 

然後就沒什麼問題了,注意對top指標的維護就好;

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 int cnt,head,tail,ans=-1,top=1;
 6 int a[100011];
 7 int stk[100011];
 8 int main()
 9 {
10     while(scanf("%d",&a[++cnt])!=EOF)continue;cnt--;
11     stk[1]=a[1];
12     for(int i=2;i<=cnt;i++)
13     {
14         int l=1,r=top,mid,tt=top+1;
15         while(l<=r)
16         {
17             mid=(l+r)/2;
18             if(stk[mid]>=a[i])l=mid+1;
19             else r=mid-1,tt=mid;
20         }
21         if(tt==top+1)top++;
22         stk[tt]=a[i];
23     }
24     printf("%d\n",top);
25     memset(stk,0,sizeof(stk));
26     stk[1]=a[1],top=1;
27     for(int i=2;i<=cnt;i++)
28     {
29         int l=1,r=top,mid,tt=top+1;
30         while(l<=r)
31         {
32             mid=(l+r)/2;
33             if(stk[mid]<a[i])l=mid+1;
34             else r=mid-1,tt=mid;
35         }
36         if(tt==top+1)top++;
37         stk[tt]=a[i];
38     }
39     printf("%d\n",top);
40     return 0;
41 }

 

 

By 淺夜_MISAKI