P1020 导弹拦截(LIS)
題目描述
某國為了防御敵國的導彈襲擊,發展出一種導彈攔截系統。但是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈能夠到達任意的高度,但是以后每一發炮彈都不能高于前一發的高度。某天,雷達捕捉到敵國的導彈來襲。由于該系統還在試用階段,所以只有一套系統,因此有可能不能攔截所有的導彈。
輸入導彈依次飛來的高度(雷達給出的高度數據是≤50000 \le 50000≤50000的正整數),計算這套系統最多能攔截多少導彈,如果要攔截所有導彈最少要配備多少套這種導彈攔截系統。
輸入輸出格式
輸入格式:
?
111行,若干個整數(個數≤100000 \le 100000≤100000)
?
輸出格式:
?
222行,每行一個整數,第一個數字表示這套系統最多能攔截多少導彈,第二個數字表示如果要攔截所有導彈最少要配備多少套這種導彈攔截系統。
?
輸入輸出樣例
輸入樣例#1: 復制 389 207 155 300 299 170 158 65 輸出樣例#1: 復制 6 2題解:
這道題之前的數據是n方的復雜度都可以過,但是在洛谷上面要nlogn的復雜度才可以,這里先講第一種
?
第一問:
就是用平常的的最長上升子序列的模板
1 for(int i=n; i>=1; --i) 2 { 3 dp[i]=1; 4 for(int j=n; j>i; --j) 5 { 6 if(v[j]<=v[i] && dp[j]+1>dp[i]) 7 dp[i]=dp[j]+1;//printf("%d %d %d %d\n",v[j],v[i],j,i); 8 } 9 maxx=max(maxx,dp[i]); 10 }但是要注意這個dp中的dp[i]表示從起始位置到i這個位置的數組長度中的最長上升子序列
例如:
1 4 3 6 5 8 ? ?? 這個序列
dp[1]是代表 ? 1? 這個序列的LIS
dp[2]是代表 ? 1 4 這個序列
...............
但是要注意這個dp[i]中的LIS一定包含第i個數(可以說最后一位已經確定)
這就導致了長度為w的序列的LIS不一定放在dp[w]中
格外注意: ? ?? 要在dp[初]------dp[w]中取最大值
?
第二問(最長上升子序列就行)之后又證明:
這個就可以用貪心算法來解決
假設給出的序列為v[]
先初始化一個空序列dp[] ? 和一個一直記錄它的長度 len
?
先把v中的第一個元素放到dp中,len初始化為1
從v的第二位開始如果大于dp[len],那我們就必須把它追在再dp數組的后面,因為這個時候按照題意必須新開一個系統
注意:這個dp序列中的值是遞增的,在之后的解釋中會發現,由此便知道dp[len]是他的最大值,如果發射的最大高度小于v中得值,那就必須新開一個系統(解釋上一句)
?
如果這個值小于dp[len],那就證明我們之前的導彈系統可以攔截到他,那我們就要更新之前的導彈系統的高度
為了弄成一個遞增序列,我們要 從dp(頭)------dp(len)來搜索一把,找到第一個,把他的之改成現在這個
?
為什么弄成遞增序列,因為可以用二分來降低復雜度,而且這只是順帶的操作
而且它既然是遞增序列了,那就證明我們取的第一個比它大的值,不是特別大,這樣也做到了最優
例如:
dp序列中有了 1 ?? 3 ?? 5 ?? 7
我們現在這個v的值是4
那我們按我們最有思想肯定是要更新5而不是7,因為7可以為了防止6的出現而新增一個系統(這個舉例為了說清楚上面那一句話)
?
操作起來就是兩個判斷
?
全部代碼:
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 const int maxn=100005; 7 int v[maxn],dp[maxn],w[maxn]; 8 int main() 9 { 10 int n=1,maxx=0; 11 memset(v,0,sizeof(v)); 12 while(~scanf("%d",&v[n])) ++n; 13 n--; 14 for(int i=n; i>=1; --i) 15 { 16 dp[i]=1; //記得初始化 17 for(int j=n; j>i; --j) //以i分界 18 { 19 if(v[j]<=v[i] && dp[j]+1>dp[i]) 20 dp[i]=dp[j]+1; 21 } 22 maxx=max(maxx,dp[i]); //不要忘了取最大值 23 } 24 int g=0; 25 w[++g]=v[1]; 26 for(int i=1;i<=n;++i) 27 { 28 if(w[g]<v[i]) //題目上面說是大于最大高度就不行了,所以等于的情況就不用特別開一個系統 29 { 30 w[++g]=v[i]; 31 } 32 else 33 { 34 for(int j=1;j<=g;++j) 35 { 36 if(w[j]>=v[i]) 37 { 38 w[j]=v[i]; 39 break; 40 } 41 } 42 } 43 } 44 printf("%d\n%d\n",maxx,g); 45 return 0; 46 } View Code?
證明第二問的方法:
參考:https://jjpjj.blog.luogu.org/dp-dao-tan-lan-jie
對于問二求整個數列的最長上升子序列即可。證明如下:
(1)假設打導彈的方法是這樣的:取任意一個導彈,從這個導彈開始將能打的導彈全部打完。而這些導彈全部記為為同一組,再在沒打下來的導彈中任選一個重復上述步驟,直到打完所有導彈。
(2)假設我們得到了最小劃分的K組導彈,從第a(1<=a<=K)組導彈中任取一個導彈,必定可以從a+1組中找到一個導彈的高度比這個導彈高(因為假如找不到,那么它就是比a+1組中任意一個導更高,在打第a組時應該會把a+1組所有導彈一起打下而不是另歸為第a+1組),同樣從a+1組到a+2組也是如此。那么就可以從前往后在每一組導彈中找一個更高的連起來,連成一條上升子序列,其長度即為K;
(3)設最長上升子序列長度為P,則有K<=P;又因為最長上升子序列中任意兩個不在同一組內(否則不滿足單調不升),則有
P>=K,所以K=P。
?
第二種方法(nlogn)
二分有一種nlogn的寫法,和上面的第二問解法一樣,但是要注意這種解法解出來的答案是正確的,但是它過程中的dp序列可能不是我們想要的答案
例如:
5 9 4 1 3 7 6 7
那么:
5 //加入
5 9 //加入
4 9 //用4代替了5
1 9 //用1代替4
1 3 //用3代替9
1 3 7 //加入
1 3 6 //用6代替7
1 3 6 7 //加入
最后b中元素的個數就是最長遞增子序列的大小,即4。
要注意的是最后數組里的元素并不就一定是所求的序列,
例如如果輸入 2 5 1
那么最后得到的數組應該是 1 5
而實際上要求的序列是 2 5
?
那么第二問和上一種方法一樣,就是用了二分
要注意如果是自己寫的二分那沒事,如果你用的系統內部函數要注意
默認是支持遞增序列
lower_bound:取大于等于的值
upper_bound:取大于的值
但是你可以在他后面加一個自己定義的比較方法,來決定lower_bound和upper_bound所取的值
例:
bool cmp(const int& a,const int& b){return a > b;}
lower_bound(a + 1, a + 1 + n, x, cmp);
或:
lower_bound(a + 1, a + 1 + n, x, greater <int> () ); ?這里的greater<int>()就是c++友情提供的方便的大于函數
?
注意得到的值指針,減去原數組就是下標
?
?
感覺沒什么了,上代碼:
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 const int maxn=100005; 7 int v[maxn],dp1[maxn],dp2[maxn]; 8 int main() 9 { 10 int n=0; 11 while(~scanf("%d",&v[++n])); 12 n--; 13 int q=0,w=0; 14 dp1[++q]=dp2[++w]=v[1]; 15 for(int i=2;i<=n;++i) 16 { 17 if(v[i]<=dp1[q]) //這里你也可以把序列反過來用LIS操作 注意等于號 18 { 19 dp1[++q]=v[i]; 20 } 21 else 22 { 23 int temp=upper_bound(dp1+1,dp1+1+q,v[i],greater<int>())-dp1; //這里得到的dp[temp]<v[i],不會等于,和原來的意義剛好相反 24 printf("%d %d\n",dp1[temp],v[i]); 25 dp1[temp]=v[i]; 26 } 27 if(v[i]>dp2[w]) //就是求原序列最長上升子序列 28 { 29 dp2[++w]=v[i]; 30 } 31 else 32 { 33 int temp=lower_bound(dp2+1,dp2+1+w,v[i])-dp2; 34 dp2[temp]=v[i]; 35 } 36 } 37 printf("%d\n%d\n",q,w); 38 return 0; 39 } View Code?
?
總結一下:感覺全部都是LIS,就是nlogn那一種方法
?
如果有錯,本菜雞求dalao指出
?
轉載于:https://www.cnblogs.com/kongbursi-2292702937/p/11006903.html
總結
以上是生活随笔為你收集整理的P1020 导弹拦截(LIS)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode-反转链表
- 下一篇: Alpha版(内部测试版)发布