组合数学及其应用——鸽巢原理

  回想到高中的的组合学中,有这样的问题,12个班中有13我的参加IOI的名额(前提每班至少出一我的),那么这会有几种分法?编程

  一个很简单的思路就是把这13个名额摊开,而后拿11个隔板插到这13个名额造成的12个空隙里,而后用组合数的公式便可计算。而鸽巢原理的简单形式就和这个模型有联系。      数组

  咱们知道,若是把12只鸽子放到11个巢里面,显然有一个巢会出现两只鸽子,这显而易见,同时也是鸽巢原理的最简单的形式。app

  它的证实也和简单,若是咱们假设11个巢穴里最多有1个鸽子,那么各自的总数最多有11个,这一12只鸽子的已知条件是不吻合的,因此假设是错误的,必定会有一个巢穴会出现2个鸽子。
  这里须要补充的一点的是,因为不一样的人举出不一样的模型来引出原理,所以原理的名字可能有所不一样,鸽巢原理还能够叫作抽屉原理,鞋盒原理。
  基于对鸽巢原理最简单模型的理解,咱们再举出几个引用来稍微更进一步的理解这个原理的应用。
  应用1:在13我的当中,必定存在2我的,他们的生日在同一个月。less

  相似的模型个以举出不少,好比366我的当中必定存在两我的是同一天生日的。 它和上面最简单的模型是同样的,这里证实就再也不累述了。ide


  应用2:假设有n对夫妇,那么最少挑出多少人,必定能保证挑出一对夫妇呢?spa

  显然是n+1我的,答案显而易见。3d


  应用3:给定m个整数a1,a2,a3……am,存在某个连续的序列(a1,a2,a3叫作连续序列,a1,a3,a4不是连续序列),这个序列的各个元素的和可以整除m。code

  咱们先考虑全部的连续序列,有不少种状况,从a1开始组合,总数有m + (m - 1) + (m + 2)……+1,可是咱们这里只需考虑从a1开始组合的连续序列。也就是考虑以下m个数。   a1  a1+a2  a1+a2+a3  a1+a2+a3+a4  …… (a1+a2+a3……+am)   咱们知道,任何一个数对m取余,可能的结果只能是[0,m-1]上的整数,而若是等于零,显然这里就符合题意了,因此咱们在考虑没有零的状况。blog

  这m个数对m取余,有m-1种状况,因此存在某两个数,对m取余的结果是同样的,假设为r,有如下等式。ip

  a1+a2……+ak = pm + r   a1+a2……+al =  qm + r   将这两个式子作减法,便可得证。


  应用4:一位国际象棋大师有11周来准备锦标赛,他计划天天至少下1局棋,可是考虑到疲劳度的问题,他一个周不会下超过12局棋。证实存在连续的若干天,这期间象棋大师刚好下了21局棋盘。

   根据题意咱们不难给出表示大师第i天下棋的总局数的一个序列——a1,a2,a3……a77,而且根据题意可知他是严格的递增的。所以咱们获得下面的不等式。

  0<a1<a2<a3……<a77<132                       序列1   

  同时咱们在构造另一个序列 a1+21,a2+21,a3+21……a77+21 ,将会获得下面的不等式。

  21<a1+21<a2+21……<a77+21<154          序列2   

  如今来看a1 a2 a3 ……a77  a1+21 a2+21 a3+21 ……a77+21这154个数,他们的取值范围是[1,153],咱们如今要任意从中取2个数字——ai,aj。那么是存在ai = aj的状况的,而根据已知的序列(a1 a2 a3 ……a77  )是严格单调,所以不可能在序列1或序列2同时取出ai,aj,所以咱们必定是从序列1取出一个数,在序列2中取出一个数,因此有ai = aj = ak + 21,此时得证。

 

  应用5:从1,2,3……200中显出101个整数。证实:在所选的这些整数之间存在两个这样的整数。其中一个可被另外一个整除。

  咱们从另外一个角度来考虑这些数字,任何一个数字能够表示成2^k * a,其中k>=0 , a是奇数,对于[1,200],中的整数,a的全部状况有1,3,5,……199这100种状况,而咱们要抽取101个数字,那么显然,会有两个数表示成以下的形式。 2^r * a , 2^s * a 而r  != s ,此时命题得证。


  应用6:设m和n是互素的正整数,并设a和b为整数,其中0<=a<=m-1 ,0<= b<=n-1。因而存在正整数x,使得x除以m的余数为a,而且x除以n的余数为b;也就是说存在一个x = pm + a, 同时x = qm + b。   为了证实咱们考虑以下n个数  a  a + m   a+ 2m   a+3m ……a+(n-1)m这n个数。

  咱们假设,这n个数里没有整除n的数(若是有的话,就符合题意了)。那么如今有n个数,而余数却有n-1种状况,根据鸽巢原理,有以下式子成立。   n = pm + a = qn + r   ①    n = im  + a = jn  + r   ② 俩式相减,咱们获得(p-i)m = (q - j)n,因为n与m互素,因此n应该是p-i的因子,而p≤n-1,显然n不多是p-i的因子,假设是不成立的。因此在这n个数中,必需要出现一个对n取余等于零的数,此时命题得证。


  

  下面给出鸽巢原理更加通常的形式:

                        

   鸽巢原理常被用于一些最大最小值的问题当中

    Ex1:

    一个果篮装有苹果、香蕉和句子。为了保证篮子中至少有8个苹果或者至少有6个香蕉或者至少有9个橘子,则放入篮子中的水果的最小件是多少?

    分析:这个问题实际上反向利用了通常形式的鸽巢原理,即通常形式的鸽巢原理具备充要性,换言之,定理1能够以下等价的表述形式:

 

    即这个问题的答案是8+6+9-3+1 = 21

 

    先来看一个简单的鸽巢原理的应用。

 

  Problem Description
  Every year there is the same problem at Halloween: Each neighbour is only willing to give a certain total number of sweets on that day, no matter how many children call on him, so it may happen that a child will get nothing if it is too late. To avoid conflicts, the children have decided they will put all sweets together and then divide them evenly among themselves. From last year's experience of Halloween they know how many sweets they get from each neighbour. Since they care more about justice than about the number of sweets they get, they want to select a subset of the neighbours to visit, so that in sharing every child receives the same number of sweets. They will not be satisfied if they have any sweets left which cannot be divided.
Your job is to help the children and present a solution.


  题目大意:给你一个含有n个元素的序列a一、a二、a三、a4……an和一个整数c,让你找一个连续的区间段(a1,a2,a3是一个连续的区间段,a1,a3,a5则不是),使得这个区间段的全部元素的和能够整除c。   数理分析:为了解决这个问题咱们考虑下面n个数字 a1  a1+a2  a1+a2+a3  a1+a2+a3+a4  a1+a2+a3+a4+a5   a1+a2+a3+a4+a5……an。

  题干中明显给出c≤n,那咱们就假设c=n吧。显然任意一个整数对c求余会获得[0,c-1]上的整数,下面咱们分两种状况来分析。

  ⑴若是这n个数字中对c取余得0,那么显然知足了条件。

  ⑵若是这n个数字对c取余没有得0的状况,那么这n个数字中,要出现n-1种取余结果,那么显然,一定存在某两个数字对c取余的余数是相同的,咱们便会获得下面的等式。(这里假设k > l,想想,为何不会相等)   

  a1+a2+a3+……ak = p*c + r  ①   

  a1+a2+a3+……al = q*c  + r  ②   两式相减咱们会获得,a(l+1) + a(l+2)……+a(k) = (p - q)*c。 显然此时咱们找到了咱们想要的区间。
  编程实现:基于上面的数理分析,再编程实现上,咱们须要获得上面讨论的这n个数,而后把取模的结果做为下标,数值做为题设给定序列的下标,在构造一个记录数组Mod,这题就能够迎刃而解。
  ac代码以下。  

 

#include<stdio.h>
#include<string.h>

int main()
{
   int Sum[100005] , Mod[100005] , A[100005];
   int c , n;
   int i;
   int right , left;
     while(~scanf("%d%d",&c , &n) && (c || n))
       {
            memset(Sum , 0 , sizeof(Sum));
            memset(Mod , -1 , sizeof(Mod));
            Mod[0] = 0;
            for(i = 1;i <= n;i++)
              {
                 scanf("%d",&A[i]);
                 Sum[i] = (Sum[i - 1] + A[i]) % c;

                 if(Mod[Sum[i]] == -1)

                        Mod[Sum[i]] = i;

                   else
                     {
                        left  = Mod[Sum[i]];
                        right = i;
                     }
              }
               for(i = left + 1;i <= right;i++)
               {
                 if(i == right)
                    printf("%d\n",i);

                 else
                    printf("%d ",i);
               }


       }
}

 


  来看一道很是简单的鸽巢原理的题目。(Problem source : 1205)

  

Problem Description
HOHO,终于从Speakless手上赢走了全部的糖果,是Gardon吃糖果时有个特殊的癖好,就是不喜欢将同样的糖果放在一块儿吃,喜欢先吃一种,下一次吃另外一种,这样;但是Gardon不知道是否存在一种吃糖果的顺序使得他能把全部糖果都吃完?请你写个程序帮忙计算一下。


  数理分析:这里就是一个上文中咱们引出鸽巢原理举出的例子。咱们从糖果数最多的“某种糖果”开始分析(由于若是数目不是最多的,是很是容易构造出不间隔的序列,而数目更多的某种糖果是不是不间隔的你是不得而知的)。

  咱们假设最多的糖果数目是maxn,咱们将这maxn个糖果排成一列,显然构造出了maxn + 1个间隔,而咱们只须要找到其余种类的糖果填掉中间的maxn - 1个间隔,就能够构造出咱们所须要的序列,这里就是体现了鸽巢原理的地方。

  编程实现:数理上的分析是很是简单的,但实际编程如何进行比较呢?在输入各类糖果并找出最大糖果数是十分好操做的,找到了最大的糖果数maxn,再如何进行比较呢?   这里咱们想,咱们还须要的是maxn - 1个糖果数,有了这些糖果,咱们就能构造出所须要的序列,而这maxn - 1个糖果是不是同一种糖果是可有可无的一件事情,因此咱们会获得一个判断式:

    sum - maxn ≥ maxn - 1.

   可能有人会疑问,这maxn - 1个糖果的种类真的是可有可无的么?若是某种糖果数大于了maxn,不就存在了不知足的状况了么?  针对第一个疑问,的确是可有可无,由于这maxn - 1 个糖果的做用是分隔那maxn - 1个糖果 ,放入“间隔”的同时其实本身也被“隔起来了”,因此糖果种类在maxn - 1个中并不起做用。而针对第二个疑问,就更显而易见来了,若是某种糖果大于maxn个,显然这与咱们先前的假设最大糖果数是maxn是不符的,所以无需给予考。
  代码以下。   

 

  #include<stdio.h>

int main()
{
      int n  , T , maxn , i , num;
      __int64 sum;

      scanf("%d",&T);
      while(T--)
        {
           scanf("%d",&n);
           maxn = 0 , sum = 0;
             while(n--)
               {
                  scanf("%d",&num);
                  if(num > maxn)
                        maxn  = num;
                  sum += num;
               }
           if(sum - maxn >= maxn - 1)  printf("Yes\n");
        else                        printf("No\n");
        }
}

 

参考系:《组合数学》 Richard A.Brualdi

相关文章
相关标签/搜索