ACM數論之旅14---抽屜原理,鴿巢原理,球盒原理(叫法不一又有什么關系呢╮(╯▽╰)╭)


 

這章沒有什么算法可言,單純的你懂了原理后會不會運用(反正我基本沒怎么用過 ̄ 3 ̄)

 

 

 

 

 

 

有366人,那么至少有兩人同一天出生(好孩子就不要在意閏年啦( ̄▽ ̄"))

有13人,那么至少有兩人同一月出生

 

這就是抽屜原理

 

抽屜原理:把n+1個物品放到n個抽屜里,那么至少有兩個物品在同一個抽屜里

鴿巢原理:把n+1個鴿子放到n個鴿巢里,那么至少有兩個鴿子在同一個鴿巢里

球盒原理:把n+1個小球放到n個球盒里,那么至少有兩個小球在同一個球盒里

(你看,我都幫你們解釋里一遍(≧︶≦*))

 

 

 

 

 

 

 

 

其實抽屜原理有兩個

第一抽屜原理

原理1: 把多於n+k個的物體放到n個抽屜里,則至少有一個抽屜里的東西不少於兩件。

原理2 :把多於mn(m乘以n)+1(n不為0)個的物體放到n個抽屜里,則至少有一個抽屜里有不少於(m+1)的物體。

原理3 :把無窮多件物體放入n個抽屜,則至少有一個抽屜里 有無窮個物體。
 
原理1 、2 、3都是第一抽屜原理的表述。

 

第二抽屜原理

把(mn-1)個物體放入n個抽屜中,其中必有一個抽屜中至多有(m—1)個物體(例如,將3×5-1=14個物體放入5個抽屜中,則必定有一個抽屜中的物體數少於等於3-1=2)。

 

 

 

 

 

 

 

 

 

原理懂了,但是你會運用嗎?

 來看這一題

cf 577B

http://codeforces.com/problemset/problem/577/B

 

 Modulo Sum

給你一個序列a1,a2...an,再給你一個數字m

 

問你能不能從中選出幾個數,把他們相加,令這個和能夠整除m

能就是輸出YES,不能就輸出NO

 

 

 

 

 

 

 

不知道你有木有思路(O ° ω ° O )

正常講肯定是dp咯,加一點剪枝,勉強卡過了(因為CF上面都是單組數據,多組可能就超時了)

AC代碼:

 1 #include<cstdio>
 2 #include<cstring> 
 3 const int N = (int)1e6 + 5;
 4 int n, m;
 5 int a[N];
 6 bool dp[2][1000];//滾動數組 
 7 bool work(){
 8     dp[0][a[0]] = true;
 9     for(int i = 1; i < n; i ++){
10         memset(dp[i & 1], 0, sizeof(bool)*1000);
11         dp[i & 1][a[i]] = true;
12         for(int j = 0; j < m; j ++){
13             if(dp[(i-1) & 1][j]){
14                 dp[i & 1][(j + a[i]) % m] = true;
15                 dp[i & 1][j] = true;
16             }
17         }
18         if(dp[i & 1][0]) return true;
19     }
20     return dp[(n-1) & 1][0];
21 }
22 int main(){
23     scanf("%d%d", &n, &m);
24     for(int i = 0; i < n; i ++){
25         scanf("%d", &a[i]);
26         a[i] %= m;
27     }
28     puts(work() ? "YES" : "NO");
29 }
View Code

 

 

 

 

其實這題的n雖然范圍大,但是我們可以加一個判斷,n>m的話,必然輸出YES

為什么?根據抽屜原理唄

先求前綴和求余m,

如果有m+1個數,那么就會產生m+1個前綴和,求余完m,就會有m+1個余數

我們知道求余完m會產生0~m-1總共m個余數

那么根據抽屜原理,至少有兩個相同的余數

那么他們之間的數的和求余m就肯定是0,所以n>m的話,必然輸出YES

 

比如

取兩個下標i和j(i < j)

(a1+a2+...+ai) % m = k

(a1+a2+...+aj) % m = k

那么(ai+...+aj) %m = 0

 

所以問題解決啦

 

AC代碼:

 1 #include<cstdio>
 2 #include<cstring> 
 3 const int N = (int)1e6 + 5;
 4 int n, m;
 5 int a[N];
 6 bool dp[2][1000];//滾動數組 
 7 bool work(){
 8     if(n > m) return true;//多加這一句,TLE的代碼說不定就能AC 
 9     dp[0][a[0]] = true;
10     for(int i = 1; i < n; i ++){
11         memset(dp[i & 1], 0, sizeof(bool)*1000);
12         dp[i & 1][a[i]] = true;
13         for(int j = 0; j < m; j ++){
14             if(dp[(i-1) & 1][j]){
15                 dp[i & 1][(j + a[i]) % m] = true;
16                 dp[i & 1][j] = true;
17             }
18         }
19         if(dp[i & 1][0]) return true;
20     }
21     return dp[(n-1) & 1][0];
22 }
23 int main(){
24     scanf("%d%d", &n, &m);
25     for(int i = 0; i < n; i ++){
26         scanf("%d", &a[i]);
27         a[i] %= m;
28     }
29     puts(work() ? "YES" : "NO");
30 }
View Code

 

 

 

 

 

 

 

 

 

 

這個原理懂了,一定要學會用,要不然碰上別的題目一樣不會(又在黑自己。。。( ̄▽ ̄"))

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM