zoukankan      html  css  js  c++  java
  • 贪心算法-例题讲解

    前言:

    此博客在写作过程中参考了大量资料和博客,不能一一列举,还请见谅。


    概述

    • 贪心法:从问题的某一个初始状态出发,逐步构造最优解从而向目标前进,并期望通过这种方法产生出一个全局最优解的方法
    • 贪心是一种解题策略,也是一种解题思想,而不是算法

    贪心策略与其他算法的区别

    • 贪心与递推:贪心法推进每一步不依据某一固定的递推式,而是当前看似最佳的贪心决策,不断的将问题归纳为更加小的相似的子问题
    • 贪心与动态规划:贪心是“鼠目寸光”;动态规划是“统揽全局”

    贪心法的优缺点

    • 优点:思维复杂度低、代码量小、运行效率高、空间复杂度低等
    • 缺点:很难找到一个简单可行并且保证正确的贪心思路

    贪心算法的应用

    贪心算法的常用范围有

    • 明显的贪心
    • 可证明贪心策略的贪心(最常见的)
    • 贪心数据结构:堆/Kruskal/Prim/Dijkstra
    • 博弈/游戏策略,这些策略大多是贪心
    • 求较优解或多次逼近求最优解

    几个简单的贪心例子

    • 最优装载问题:给n个物体,第i个物体重量为wi,选择尽量多的物体,使得总重量不超过C
      贪心策略:先拿轻的
    • 部分背包问题:有n个物体,第i个物体的重量为wi,价值为vi,在总重量不超过C的情况下让总价值尽量高。每一个物体可以只取走一部分,价值和重量按比例计算
      贪心策略:先拿性价比高的
    • 乘船问题:有n个人,第i个人重量为wi。每艘船的载重量均为C,最多乘两个人。用最少的船装载所有人
      贪心策略:最轻的人和最重的人配对

    例题(基础)

    1.删数问题

    -【问题描述】键盘输入一个高精度的正整数n(n<=240位),去掉其中任意s个数字后剩下的数字按照原来的次序将组成一个新的正整数。编程对给定的n和s,寻求一种方案,使得剩下组成的新数最小。

    • 贪心策略为:每一步总是选择一个使剩下的数最小的数字删去,即按高位到低位的顺序搜索,若各位数字递增,则删除最后一个数字,否则删除第一个递减区间的首字符。然后回到串首,按上述规则再删除下一个数字。重复以上过程s次,剩下的数字串便是问题的解了。例如对n=178543,s=4,删数的过程如下:
      n=178543 {删掉8}
      n=17543 {删掉7}
      n=1543 {删掉5}
      n=143 {删掉4}
      n=13 {解为13}

    2.排队打水问题(进阶)

    • 【问题描述】有n个人排队到r个水龙头去打水,他们装满水桶的时间T1、T2…,Tn为整数且各不相等,应如何安排他们的打水顺序才能使他们总共花费的时间最少?
    • 越靠前面的计算的次数越多,显然越小的排在越前面得出的结果越小
    • 核心代码
    cin>>n>>t;
       for(i=1;i<=n;i++)cin>>a[i];
       qsort(1,n); //按照打水时间从小到大快排
       j=0;k=0;
       for(i=1;i<=n;i++)//对n个人依次安排
       {  j++;
          if(j==t+1)j=1;//j依次枚举打水龙头,k为累加总共打水时间
          b[j]+=a[i];  //记录第j个水龙头的花费时间
          k+=b[j];   //累加总时间
       }
       cout<<k<<endl;
    
    

    3.取数游戏

    • 【问题描述】给出2n(n<=100)个自然数(小于等于30000)。将这n个自然数排成一列,游戏双方A和B从中取数,只允许从两端取数。A先取,然后双方轮流取数。取完时,谁取得数字总和最大为取胜方;若双方和相等,属B胜。试问A方是否有必胜策略?
    • 我们发现一个有趣的事实:A方取走偶位置的数后,剩下两端数都处于奇位置;反之,若A方取走奇位置的数后,剩下两端数都处于偶位置。即无论B方如何取法,A方即可以取走奇位置的所有数,亦可以取走偶位置的所有数。由此一种有效贪心策略:若能够让A方取走“数和较大的奇(或偶)位置上的所有数”,则A方必胜。

    4.打包

    • 【问题描述】某工厂生产出的产品都要被打包放入正四棱柱的盒子内,所有盒子的高度为h,但地面尺寸不同,可以为 1x1、2x2、3x3、4x4、5x5、6x6。如图:

      这些盒子将被放入高度为h,地面尺寸为6x6的箱子中。为了降低运送成本,工厂希望尽量减少箱子的数量。如果有一个好算法,能使箱子的数量降到最低,这将给工厂节省不少的资金。请你编写一个程序。
    • 难点在于细节。A[i]表示i*i的正方形个数。
    • 核心代码
    int a[7],i,k,tot;
     for(i=1;i<=6;i++)cin>>a[i];  //读入数量
       tot=a[6]+a[5]+a[4];  //至少得盒子数量
       a[1]=a[1]-11*a[5];a[2]=a[2]-a[4]*5;
       if(a[2]<0){a[1]=a[1]+a[2]*4;a[2]=0;}
       tot=tot+a[3]/4;
       a[3]=a[3]%4;
       if(a[3]==3){tot++;if(a[2]!=0){a[2]--;a[1]-=5;}else a[1]-=9;}
       if(a[3]==2){tot++;k=min(a[2],3);a[2]-=k;a[1]-=18-k*4;}
       if(a[3]==1){tot++;k=min(a[2],5);a[2]-=k;a[1]-=27-k*4;}
       if(a[2]>0){tot=tot+a[2]/9; a[2]=a[2]%9;}
       if(a[2]>0){tot++;a[1]-=36-a[2]*4;}
       if(a[1]>0)tot+=a[1]/36+1;
       cout<<tot<<endl;
    

    5.均分纸牌 题解

    • 【问题描述】有N堆纸牌,编号分别为 1,2,…,N。每堆上有若干张,但纸牌总数必为N的倍数。可以在任一堆上取若于张纸牌,然后移动。
      移牌规则:编号为1堆上取的纸牌,只能移到编号为2的堆上;编号为N的堆上取的纸牌,只能移到编号为N-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
      现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
    • 我们要使移动次数最少,就是要把浪费降至零。通过对具体情况的分析,可以看出在某相邻的两堆之间移动两次或两次以上,是一种浪费,因为我们可以把它们合并为一次或零次。
      采用“移动一次使得一堆牌数达到平均值”的贪心策略:先把每堆的牌数减去平均数,然后由左而右的顺序移动纸牌。若第i堆纸牌的张数a[i]不为0,则将值移动到下一堆

    6.【HAOI2008】糖果传递 题解

    • 【问题描述】有n个小朋友坐成一圈,每人有ai个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为1。求使所有人获得均等糖果的最小代价。
    • 和均分纸牌类似,现在假设编号为i的人初始有Ai个糖果。对于1号来说,他给了n号x1个糖果,还剩A1-x1个;但是因为2号给了他x2个糖果,所以最后还剩A1-x1+x2个糖果。根据题设,该金币数等于M。换句话说,我们得到了一个方程:A1-x1+x2=M。
      同理,对于第2个人,有A2-x2+x3=M。最终,我们可以得到n个方程,一共n个变量,是不是可以直接解方程组了呢?很可惜,还不行。因为从前n-1个方程可以推导出最后一个方程。所以,实际上只有n-1个方程是有用的。
      尽管无法直接解出答案,我们还是可以尝试着用x1表示出其他的xi,则本题就变成了单变量的极值问题。
      对于第1个人,A1-x1+x2=M→x2=M-A1+x1=x1-C1(令C1=A1-M,下面类似)
      对于第2个人,A2-x2+x3=M→x3=M-A2+x2=2M-A1-A2+x1=x1-C2(C2=C1+A2-M)
      对于第3个人,A3-x3+x4=M→x4=M-A3+x3=3M-A1-A2-A3+x1=x1-C3
      .....
      对于第1个人,An-xn+x1=M。这是一个多余的等式,并不能给我们更多的信息。
      我们希望所有的xi的绝对值之和尽量小,即|x1|+|x1-C1|+|x1-C2|+...+|x1-Cn-1|要最小。注意到|xi-Ci|的集合意思是数轴上点x1到Ci的距离,所以问题变成了:给定数轴上的n个点,找出一个到它们的距离之和尽量小的点。
      结论:给定数轴上的n个点,在数轴上的所有点中,中位数离所有顶点的距离之和最小。凡是能转化为这个模型的题目都可以用中位数求解。
    • 核心代码
    const int MaxN=1000005;
    long long a[MaxN],C[MaxN],tot,M;
    int main()
    {  cin>>n;
       tot=0;
       for(i=1;i<=n;i++){cin>>a[i];tot+=a[i];}//读入数据求出总和
       M=tot/n;
       for(i=1;i<=n;i++)c[i]=c[i-1]+a[i]-M;//递推求c数组的前缀和
       sort(c+1,c+n+1);//从小到大快排
       long long x1=c[n/2],ans=0;//„计算一维中位数的和
       for(i=1;i<=n;i++)ans+=abs(x1-c[i]);
       cout<<ans<<endl; 
    }
    

    7.数列极差问题 题解

    • 【问题描述】佳佳的老师在黑板上写了一个由n个正整数组成的数列,要求佳佳进行如下操作:每次擦去其中的两个数a和b,然后在数列中加入一个数a×b+1,如此下去直至黑板上剩下一个数为止,在所有按这种操作方式最后得到的数中,最大的为max,最小的为min, 则该数列的极差定义为M=max-min。由于佳佳忙于准备期末考试,现请你帮助他,对于给定的数列,计算出相应的极差d。
    • 所以若使第k(1<=k<=N-1)次变换后所得值最大,必使(k-1)次变换后所得值最大(符合贪心策略的特点2),在进行第k次变换时,只需取在进行(K-1)次变换后所得数列中的两个最小数p,q进行合并操作:p←pq+1,q←∞即可(符合贪心策略特点1),因此此题可用贪心策略求解。讨论完毕。在求min时,我们只需在每次变换的数列中找到两个最大数p,q进行合并操作:p←pq+1,q←-∞即可,原理同上。
      综上所述,先合并小数得到最大值;先合并大数得到最小值。

    8.潜水比赛

    • 【问题描述】在马其顿王国的ohide湖里举行了一次潜水比赛。其中一个项目是从高山上跳下水,再潜水到达终点。这是一个团体项目,一支队伍由n个人组成。在潜水时必须使用氧气瓶,但是每支队伍只有一个氧气瓶。最多两个人同时使用一个氧气瓶,但此时两人必须同步游戏,因此两人达到终点的时间等于较慢的一个人单独游到终点所需要的时间。好在大家都很友好,因此任何两个人都愿意一起游泳。安排一种潜水的策略,使得最后一名选手尽量早到终点。
    • 方法一N个人:每个人所需的时间:t1,t2,……tn。假设t1最小。每次由t1接送人和氧气瓶,则总时间:s=t2+t3+...tn+(n-2)*t1
    • 方法二将n个人的时间从小到达排序,假设从小到大为:t1,t2,……tn
      t1和t2过:t2
      t1带瓶返回:t1
      最大的两个人:tn tn-1过:tn
      t2带瓶返回:t2
      把以上看作一趟:把用时最长的两个人tn,tn-1送过去 用时:2*t2+t1+tn
      重复上述过程:用t1和t2在把tn-2,tn-3送过去,用时2*t2+t1+tn-2,每趟都用t1和t2,每趟运送2人。
    • 方法三每一趟送用时间最长的两个人时:根据情况选择:用t1和t2两个人还是只用t1一个人。
      用t1和t2送一趟用时:x=2*t2+t1+tn
      用t1一个人送一趟(2人):y=2*t1+tn+tn-1
      每送一趟都要比较x和y的大小:If(x>y)用t1送;else 用t1和t2送
    • 核心代码
    cin>>n;
    for(int i=1;i<=n;i++)cin>>t[i];
    sort(t+1,t+n+1,cmp);
    f[1]=t[1];f[2]=t[2];
    for(int i=3;i<=n;i++) f[i]=min(f[i-1]+t[1]+t[i],f[i-2]+t[1]+t[i]+2*t[2]);
    cout<<f[n]<<endl;
    

    后记:

    贪心是一种思想,简单贪心无脑题,复杂贪心难想难证明,和其他东西合在一起。。。蒟蒻只能说一句:多看题,看运气

  • 相关阅读:
    python 产生token及token验证
    Django中间件
    docker学习笔记16:Dockerfile 指令 ADD 和 COPY介绍
    Docker 容器镜像删除
    linux查找nginx所在目录
    nginx启动访问
    nginx安装【linux下安装】
    QPS计算
    Jmeter压测问题_Non HTTP response code: org.apache.http.conn.ConnectTimeoutException
    Jmeter压测问题_Non HTTP response code: java.net.ConnectException
  • 原文地址:https://www.cnblogs.com/wuwendongxi/p/13334512.html
Copyright © 2011-2022 走看看