區賽第一題講解+基礎算法——桶排序與快速排序
截止到上篇隨筆,我們已經學完了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; }
假設輸入:
5100 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
區賽第一題講解+基礎算法——桶排序與快速排序