区赛第一题讲解+基础算法——桶排序与快速排序

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

以上是关于区赛第一题讲解+基础算法——桶排序与快速排序的主要内容,如果未能解决你的问题,请参考以下文章

每日一题 | 如何快速过河 | 桶排序与双指针

算法浅谈——分治算法与归并快速排序(附代码和动图演示)

0基础学算法 第二弹 排序

三种线性排序算法:计数排序桶排序与基数排序

排序!——神奇的排序!!

算法:计数排序与桶排序