1. 程式人生 > >九度1131:合唱隊形

九度1131:合唱隊形

實驗 memset size 們的 i+1 一點 () [] ctrl+

**********************************************************************************

曬題:

題目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:合唱隊形