组合数学及其应用——鸽巢原理
回想到高中的的组合学中,有这样的问题,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
先来看一个简单的鸽巢原理的应用。
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)
数理分析:这里就是一个上文中我们引出鸽巢原理举出的例子。我们从糖果数最多的“某种糖果”开始分析(因为如果数目不是最多的,是非常容易构造出不间隔的序列,而数目更多的某种糖果是否是不间隔的你是不得而知的)。
我们假设最多的糖果数目是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