動態規劃--尋找最長遞減子序列
昨天C++課上留了三道題,除了C語言本身外都涉及了一些演算法。其中第二個問題是這樣的:
攔截導彈
某國為了防禦敵國的導彈襲擊,開發出一種導彈攔截系統。但是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈能夠到達任意的高度,但是以後每一發炮彈都不能高於前一發的高度。某天,雷達捕捉到敵國的導彈來襲,並觀測到導彈依次飛來的高度,請計算這套系統最多能攔截多少導彈。攔截來襲導彈時,必須按來襲導彈襲擊的時間順序,不允許先攔截後面的導彈,再攔截前面的導彈。
輸入
第一行,輸入雷達捕捉到的敵國導彈的數量k(k<=25),
第二行,輸入k個正整數,表示k枚導彈的高度,按來襲導彈的襲擊時間順序給出,以空格分隔。
輸出
輸出只有一行,包含一個整數,表示最多能攔截多少枚導彈。
樣例輸入
8
300 207 155 300 299 170 158 65
樣例輸出
6
課堂上沒搞出來。老師說用到了動態規劃。好吧,高中沒學過資訊學競賽。不過沒關係,回去翻了翻《演算法導論》,基本算弄明白了。其實就是求一串數的最長遞減子序列。
問題關鍵在於,可能的情況組合很多,不適合窮舉。所以要利用動態規劃(很適合這類問題)。動態規劃把問題分成很多子問題,而子問題之間又有關係。子問題不是相互獨立的,子問題可能有重疊。這和分治法(典型的比如歸併排序)不一樣。動態規劃的基本想法在於:1.描述最優解結構,就是說最優解是什麼樣子的,符合什麼條件。2.遞迴定義最優解的值。3.自底向上計算最優解的值。4.由計算的值構造最優解(在這題裡沒要求,到第三部求出序列長就行了)。
這個問題中比如說要攔第i個導彈。截止到第i個導彈的最長攔截序列肯定包含了前面i-1個導彈中最長的序列。如果不是這樣,我們求出的截止到第i個導彈的截序列就不是最長的了。我們用陣列D[i]把截止到第i個導彈的最長攔截序列長度存起來。最後計算完i=k(導彈總數)時,整個問題的答案就是陣列D[i]的最大值。
如果用一個數組,把前一個最優解對應的導彈編號存下來,最後還可以把被擊落的導彈高度按編號輸出。
程式碼:
/* 設陣列A存放導彈高度,D[i]是高度為A[i]是的最優解,S用於記錄最優解中導彈被擊落的次序 最優解結構:設在第k個數處的最優解為D(k)。設當前位於第i個數,則在第i個數處的 最優解D(i)為:在i之前攔下導彈最多的方法的解D(j)加上1。(因為把第i 個也打下來了,所以加上1)。當然還要保證A[i]<=A[j] 遞迴解:D(i)=D(j)+1。j是陣列A中位於A[i]左側,且大於等於A[i]的所有數中D值最大的 那個。如果A[i]左側沒有找到大於等於它的數,則D[i]=1。 最終結果:找到陣列D中最大的數,這個數就是結果。 */ #include <cstdio> int main(){ int k; scanf("%d", &k); int A[k], D[k], S[k]; for(int i=0; i<k; i++){ scanf("%d", &A[i]); D[i]=0; } D[0]=1; for(int i=1; i<k; i++){ bool found=false; //是否找到i之前的最優解 int last_good_solve=D[0]; int good_position=0;//最優解的下標和值 for(int j=0; j<i; j++) if(A[j]>=A[i] && D[j]>=D[good_position]){ last_good_solve=D[j]; good_position=j; found=true; } if(found){ D[i]=1+D[good_position]; S[i]=good_position; } else { D[i]=1; S[i]=good_position; } } int result=0; int go_back;//用於向前找被擊落的導彈 for(int i=0; i<k; i++){ if(D[i]>result){ result=D[i]; go_back=i; } } printf("%d\n", result); for(int i=0; i<result; i++){ printf("%d ", A[go_back]); go_back=S[go_back]; }//逆序輸出被擊落導彈的高度 }