回想到高中的的組合學中,有這樣的問題,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