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

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

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

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

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

  类似的模型个以举出很多,比如366个人当中一定存在两个人是同一天生日的。 它和上面最简单的模型是一样的,这里证明就不再累述了。


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

  显然是n+1个人,答案显而易见。


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

  我们先考虑所有的连续序列,有很多种情况,从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]上的整数,而如果等于零,显然这里就符合题意了,所以我们在考虑没有零的情况。

  这m个数对m取余,有m-1种情况,所以存在某两个数,对m取余的结果是一样的,假设为r,有以下等式。

  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个元素的序列a1、a2、a3、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

posted on 2016-05-09 08:49  在苏州的城边  阅读(5280)  评论(1编辑  收藏  举报

导航