1. 程式人生 > >區賽第一題講解+基礎算法——桶排序與快速排序

區賽第一題講解+基礎算法——桶排序與快速排序

long 題目 n) 合並 ++ ace das 如何 作用

  截止到上篇隨筆,我們已經學完了c++中所有的基礎語句,這意味著,noip普及組的第一題你已經可以拿滿分了。為了紀念這個偉大的時刻,今天要上的這道題,是剛剛考完的海澱區區賽第一題。

題目描述:
已知RFdragon有n個杯子,每個杯子的容積都是無限大,裏面都裝有1L水。由於RFdragon的杯子實在太多了,他決定扔掉一些杯子,使剩下的杯子不超過k個。RFdragon每次可以把兩個裝水體積相同的杯子中的水倒在其中一個杯子裏,然後扔掉另一個杯子。有時候,RFdragon無論如何也不能使剩下的杯子不超過k個,因此只能去商店裏買一些杯子(初始裝有1L水)。RFdragon最少需要買多少杯子呢?

輸入描述:
兩個整數,中間用空格隔開。

輸出描述:
一個正整數ans,表示最少需要買ans個杯子。

輸入樣例:
3 1 輸出樣例: 1 其他說明: 0<k<n<2^31

  這道題比較復雜,我當時大概花了半小時做這道題,稍後我會詳細講解。

  今天我們終於可以進入算法部分。我們來講講一種非常實用的算法——排序算法。說到算法,就不得不說一說算法的時間復雜度問題。時間復雜度其實就是運算次數,但計算時間復雜度時可以忽略較小的常數。時間復雜度的符號用O表示,如計算n個數的和的時間復雜度是O(n)。算法的主要作用,就是在保證程序功能完整的情況下,盡可能降低時間復雜度,來使代碼運行速度大大提高。

  排序算法主要有9種,作用都是將一些數按一定規則進行排序。在這裏我要和大家說,桶排序是九大排序算法中時間復雜度最低的一個,厲害吧?那麽,桶排序是怎樣實現的呢?我們來設想一個情景。假設你(為了在考試後找到心理安慰)要對一些同學的成績從小到大進行排序,其中每個數都不超過100。這時,你可以準備101個桶,把它們編號為0~100,對應0~100之間的所有分數。你拿起了第一份試卷,發現是100分(又看了一眼名字發現不是自己的,心理壓力瞬間增大),就把它扔進100號桶裏。又拿起一份試卷,發現是99分(又看了一眼名字發現不是自己的,心理壓力瞬間增大),就把它扔進99號桶裏……你拿起了最後一份試卷,發現是0分(又看了一眼名字發現是自己的,心理壓力max),就把它扔進0號桶裏。這時,對於從0~100的所有分數你就知道這些分數有多少人了。隨後,我們只需要將他們從小到大輸出就可以了。你會發現,如果開一個101位的數組,那他的101個變量的下標剛好對應桶的編號。廢話不多說,直接上代碼:

#include<cstdio>
int n,a,buc[101];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a);
        buc[a]++;
    }
    for(int i=0;i<=100;i++)
        for(int j=1;j<=i;j++)
            printf("%d ",i);
    return 0;
}

  假設輸入:

5
100 99 78 99 0

  運行結果為:

0 78 99 99 100

  是不是非常簡單?作為最快的排序算法,桶排序的時間復雜度僅僅是O(n)!但問題是,我們這裏保證所有數不超過100,但如果範圍擴大到int或long long,那桶排序就無能為力了。因此,我們需要一個新算法,來對更大的數進行排序,這就是快速排序。快速排序非常復雜,簡單地說,就是每次找一個基準數,把比基準數小的數放在他左邊,把比基準數大的數放在他右邊,直到所有數都排序完畢為止。先不詳細講了,直接上代碼:

void Quicksort(int a[],int head,int tail)
{
    int jz,l=head,r=tail;
    if(head<tail)
    {
        jz = a[head];
        while(l<r)
        {
            while(l<r&&a[r]>=jz)
                r--;
            if(l<r)
            {
                a[l]=a[r];
                l++;
            }
            while(l<r&&a[l]<jz)
                l++;
            if(l<r)
            {
                a[r]=a[l];
                r--;
            }
        }
        a[l]=jz;
        Quicksort(a,head,l-1);
        Quicksort(a,l+1,tail);
    }
}

  之所以不細講,是因為algorithm中的sort就是快速排序,並且功能更加全面。我將在下篇隨筆中詳細講解sort的用法。

  最後,我們來講講區賽的這道題。根據題意我們知道,每個杯子中裝的水只能是2^0、2^1、2^2……因此,我們用一個bin數組。其中,bin[i]表示裝水為2^i的杯子的個數。這裏講一下一個比較超綱的知識——按位與&,它是位運算的一種,如果整數a轉成2進制後最後一位是1,那麽a&1為true,否則為false。>>=1和除以2是一樣的。以下是初始化的代碼:

#include<cstdio>
int n,k,bin[32],num;
int main()
{
    scanf("%d %d",&n,&k);
    for(int i=0;n;i++)//將n轉成2進制並存在數組bin中 
    {
        if(n&1)//二進制最後一位為1 
        {
            bin[i]++;//數組對應位置+1 
            num++;//目前的杯子數+1 
        }
        n>>=1;//與n/=2等價,但運算速度更快 
    }
}

  之所以將n轉成二進制,是因為二進制中“1”的個數就等於在不買杯子的情況下,合並後的杯子個數,也就是num的值。

  這時,如果num<=k,程序就結束了,否則的話,我們就需要買杯子。每次我們可以買2^i個杯子,是bin[i]加1。那什麽時候我們會買杯子呢?我們從bin的最低位開始,對於每一位bin[i],如果那一位是0,當然就不需要買杯子;如果是1,那我們就要買2^i個杯子,使bin[i]=0,bin[i+1]++。這樣的話,我們有時會遇到bin[i]=2的情況,我們就使bin[i]=0,bin[i+1]++,只有這種情況才能省出杯子。完整代碼如下:

#include<cmath>
#include<cstdio>
using namespace std;
int n,k,bin[32],num,ans;//用ans存答案 
int main()
{
    scanf("%d %d",&n,&k);
    for(int i=0;n;i++)//將n轉成2進制並存在數組bin中 
    {
        if(n&1)//二進制最後一位為1 
        {
            bin[i]++;//數組對應位置+1 
            num++;//目前的杯子數+1 
        }
        n>>=1;//與n/=2等價,但運算速度更快 
    }
    for(int i=0;num>k;i++)//除非num<=k,否則仍要買杯子 
    {
        if(bin[i]==1)
        {
            bin[i]=0;
            bin[i+1]++;
            ans+=pow(2,i);
        }
        if(bin[i]==2)
        {
            bin[i]=0;
            bin[i+1]++;
            num--;
        }
    }
    printf("%d",ans);
    return 0;
}
//以下是使用快速冪的版本,你可能看不懂,但他會大大降低時間復雜度
//#include<cstdio>
//int n,k,bin[32],num,ans;
//int qpow(int x,int y)//快速冪 
//{
//    int z=y,base=x,sum=1;
//    while(z)
//    {
//        if(z&1)
//            sum*=base;
//        base*=base;
//        z>>=1;
//    }
//    return sum;
//} 
//int main()
//{
//    scanf("%d %d",&n,&k);
//    for(int i=0;n;i++)
//    {
//        if(n&1)
//        {
//            bin[i]++;
//            num++;
//        }
//        n>>=1;
//    }
//    for(int i=0;num>k;i++)
//    {
//        if(bin[i]==1)
//        {
//            bin[i]=0;
//            bin[i+1]++;
//            ans+=qpow(2,i);
//        }
//        if(bin[i]==2)
//        {
//            bin[i]=0;
//            bin[i+1]++;
//            num--;
//        }
//    }
//    printf("%d",ans);
//    return 0;
//}

Created by RFdragon

區賽第一題講解+基礎算法——桶排序與快速排序