九度1131:合唱隊形
**********************************************************************************
曬題:
題目1131:合唱隊形
時間限制:1 秒
內存限制:32 兆
特殊判題:否
提交:5059
解決:1601
- 題目描述:
-
N位同學站成一排,音樂老師要請其中的(N-K)位同學出列,使得剩下的K位同學不交換位置就能排成合唱隊形。
合唱隊形是指這樣的一種隊形:設K位同學從左到右依次編號為1, 2, …, K,他們的身高分別為T1, T2, …, TK,
則他們的身高滿足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。
你的任務是,已知所有N位同學的身高,計算最少需要幾位同學出列,可以使得剩下的同學排成合唱隊形。
- 輸入:
-
輸入的第一行是一個整數N(2 <= N <= 100),表示同學的總數。
第一行有n個整數,用空格分隔,第i個整數Ti(130 <= Ti <= 230)是第i位同學的身高(厘米)。
- 輸出:
-
可能包括多組測試數據,對於每組數據,
輸出包括一行,這一行只包含一個整數,就是最少需要幾位同學出列。
- 樣例輸入:
-
8 186 186 150 200 160 130 197 220
- 樣例輸出:
-
4
- 來源:
- 2008年北京大學方正實驗室計算機研究生機試真題
一直在自學王道的書,昨天開始進入dp階段,先做了攔截導彈,但死活通不過,給的測試案例是可以過的,在OJ上就WA,十分郁悶。今天做這道題,寫最大上升子序列的部分時幡然醒悟,昨天的算法錯了。。。。昨天的LIS部分我是這麽寫的:
for(i=2;i<=k;i++){ ///對於以a[k]結尾的子序列 for(j=i-1;j>=1;j--){ if(a[i]<=a[j]&&f[i]<=f[j]+1){ f[i]=f[j]+1; break; } } if(j<1) f[i]=1; }
即對於每一個數列中的元素,我沒有從前面開始遍歷,而是從這個元素開始逐個往前找,找到一個滿足條件的直接加1就跳出循環了,這樣是不對的,有可能時結果變小了。。正確的算法應該是從第一個元素開始遍歷,一會我會把相應部分加粗。坑爹的是這麽寫的話本地案例居然可以過。。。
再說這道題,我又被scanf函數給坑了,本來已經寫好了一版代碼,如下:
#include <cstdio> #include <cstring> #include <iostream> using namespace std; int t[101]; int rev_t[101]; void revv(int a[],int m) { for(int i=0;i<m;i++){ rev_t[m-i-1]=a[i]; } } void find_long_up(int a[],int num,int b[]) { int i,j; for(i=1;i<num;i++){ for(j=0;j<i;j++){ if(a[i]>a[j]&&b[i]<b[j]+1) b[i]=b[j]+1; } } } int main() { int n,i; while(scanf("%d",&n)!=EOF){ int max=1; int f_long_up[101]; int f_long_up_rev[101]; for(i=0;i<n;i++){ f_long_up[i]=1; f_long_up_rev[i]=1; } for(i=0;i<n;i++){ scanf("%d",&t[i]); } find_long_up(t,n,f_long_up); revv(t,n); find_long_up(rev_t,n,f_long_up_rev); for(i=0;i<n;i++){ if(f_long_up[i]+f_long_up_rev[n-1-i]>max) max=f_long_up[i]+f_long_up_rev[n-1-i]; } printf("%d\n",n-max+1); } return 0; }
本來while循環條件那裏,我沒有寫EOF,結果就一直超時。。。後來才發現如果不加的話while會變成死循環的,ctrl+z都退不出來。。可我剛開始不知道,百思不得其解,覺的復雜度和邊界條件都沒問題,於是寫了第二版代碼:
#include <cstdio> int main() { int n,i,j; while(scanf("%d",&n)!=EOF){ int t[n]; int f1[n],f2[n]; for(i=0;i<n;i++){ scanf("%d",&t[i]); f1[i]=1;f2[i]=1; } for(i=1;i<n;i++){ for(j=0;j<i;j++){ if(t[i]>t[j]&&f1[i]<f1[j]+1) f1[i]=f1[j]+1; } } for(i=n-2;i>-1;i--){ for(j=n-1;j>i;j--){ if(t[i]>t[j]&&f2[i]<f2[j]+1) f2[i]=f2[j]+1; } } int mx=1; for(i=0;i<n;i++){ if(f1[i]+f2[i]-1>mx) mx=f1[i]+f2[i]-1; } printf("%d\n",n-mx); } }
剛寫出來也沒加EOF,於是再次超時。。。我就開始懷疑了,可能是哪裏進入死循環了。。拿ctrl+z試了一下果然沒退出來......我就知道了.......
上面兩版代碼都能用,第一種用了子函數,第二種直接全在主函數中處理的。算法很明確,就是正反各用一次LIS,但有很多細節需要註意,比如代碼2的LIS部分,正反兩個循環,就必須分開,不能都合在第一個裏,因為計算過程中,當前待計算的元素之前的所有元素都必須已經被計算過了,如果都合在
for(i=1;i<n;i++)
這個循環裏,那麽計算f2[i]時,後面的f2[j]其實都是沒有被算過的,結果必然不可能總是準的(有一些情況可能碰上,比如本地案例......)。
這是其一,其二,最終輸出結果時,由於正反兩次序列中間的元素被算了兩次,所以要減1.
再說說第一版代碼,由於多組數據,每次測試f_long_up和f_long_up_rev這兩個數組肯定要更新的,開始時我用memset。。結果果斷被坑。。。。大家註意,這個函數很危險,它是以字節為單位來初始化內存的,第三個參數必須是sizeof(類型)*個數,才能不會錯。這還不算,它一般被用來初始化為全0.。如果初始化值換成個別的,比如1,那就慘了,因為因為他會把數組元素都變成0000 0001 0000 0001 0000 0001 0000 0001........因為int是4個字節的......這是一個很大的數,程序自然不正常了。結論:他很危險。還是用for循環或在聲明時初始化吧。
還有一點,是從別人學到的。有人說用cin和cout代替printf 和scanf,會超時。。我試了一下,果然這樣。盡管“while(cin>>n)”這麽寫不加EOF也可以ctrl+z退出(正因為此,我誤以為scanf也可以這樣。。),但就是超時。看了知乎明白了。
總之,今天一天終於掌握了LIS~
****************************************************************************************************
堅持,而不是打雞血
九度1131:合唱隊形