区赛第一题讲解+基础算法——桶排序与快速排序
Posted rfdragon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区赛第一题讲解+基础算法——桶排序与快速排序相关的知识,希望对你有一定的参考价值。
截止到上篇随笔,我们已经学完了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
以上是关于区赛第一题讲解+基础算法——桶排序与快速排序的主要内容,如果未能解决你的问题,请参考以下文章