組合數取模

組合數取模在ACM競賽中是一個很重要的問題,很多選手因爲數據太大而束手無策,今天就來詳細講解它。

 

組合數取模就是求的值,當然根據的取值範圍不同,採取的方法也不一樣。

 

接下來,我們來學習一些常見的取值情況

 

(1)

 

     這個問題比較簡單,組合數的計算可以靠楊輝三角,那麼由於的範圍小,直接兩層循環即可。

 

(2),並且是素數

 

     這個問題有個叫做Lucas的定理,定理描述是,如果

 

     

 

     那麼得到

 

     

   

     這樣然後分別求,採用逆元計算即可。

 

 

題目:http://acm.fzu.edu.cn/problem.php?pid=2020

 

題意:,其中,並且是素數。

 

代碼:

  1. #include <iostream>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4.   
  5. using namespace std;  
  6. typedef long long LL;  
  7.   
  8. LL n,m,p;  
  9.   
  10. LL quick_mod(LL a, LL b)  
  11. {  
  12.     LL ans = 1;  
  13.     a %= p;  
  14.     while(b)  
  15.     {  
  16.         if(b & 1)  
  17.         {  
  18.             ans = ans * a % p;  
  19.             b--;  
  20.         }  
  21.         b >>= 1;  
  22.         a = a * a % p;  
  23.     }  
  24.     return ans;  
  25. }  
  26.   
  27. LL C(LL n, LL m)  
  28. {  
  29.     if(m > n) return 0;  
  30.     LL ans = 1;  
  31.     for(int i=1; i<=m; i++)  
  32.     {  
  33.         LL a = (n + i - m) % p;  
  34.         LL b = i % p;  
  35.         ans = ans * (a * quick_mod(b, p-2) % p) % p;  
  36.     }  
  37.     return ans;  
  38. }  
  39.   
  40. LL Lucas(LL n, LL m)  
  41. {  
  42.     if(m == 0) return 1;  
  43.     return C(n % p, m % p) * Lucas(n / p, m / p) % p;  
  44. }  
  45.   
  46. int main()  
  47. {  
  48.     int T;  
  49.     scanf("%d", &T);  
  50.     while(T--)  
  51.     {  
  52.         scanf("%I64d%I64d%I64d", &n, &m, &p);  
  53.         printf("%I64d\n", Lucas(n,m));  
  54.     }  
  55.     return 0;  
  56. }  


由於上題的比較大,所以組合數只能一個一個計算,如果的範圍小點,那麼就可以進行階乘預處理計算了。

 

(3),並且可能爲合數

 

    這樣的話先採取暴力分解,然後快速冪即可。

 

題目:http://acm.nefu.edu.cn/JudgeOnline/problemshow.php?problem_id=628

 

代碼:

  1. #include <iostream>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4.   
  5. using namespace std;  
  6. typedef long long LL;  
  7. const int N = 200005;  
  8.   
  9. bool prime[N];  
  10. int p[N];  
  11. int cnt;  
  12.   
  13. void isprime()  
  14. {  
  15.     cnt = 0;  
  16.     memset(prime,true,sizeof(prime));  
  17.     for(int i=2; i<N; i++)  
  18.     {  
  19.         if(prime[i])  
  20.         {  
  21.             p[cnt++] = i;  
  22.             for(int j=i+i; j<N; j+=i)  
  23.                 prime[j] = false;  
  24.         }  
  25.     }  
  26. }  
  27.   
  28. LL quick_mod(LL a,LL b,LL m)  
  29. {  
  30.     LL ans = 1;  
  31.     a %= m;  
  32.     while(b)  
  33.     {  
  34.         if(b & 1)  
  35.         {  
  36.             ans = ans * a % m;  
  37.             b--;  
  38.         }  
  39.         b >>= 1;  
  40.         a = a * a % m;  
  41.     }  
  42.     return ans;  
  43. }  
  44.   
  45. LL Work(LL n,LL p)  
  46. {  
  47.     LL ans = 0;  
  48.     while(n)  
  49.     {  
  50.         ans += n / p;  
  51.         n /= p;  
  52.     }  
  53.     return ans;  
  54. }  
  55.   
  56. LL Solve(LL n,LL m,LL P)  
  57. {  
  58.     LL ans = 1;  
  59.     for(int i=0; i<cnt && p[i]<=n; i++)  
  60.     {  
  61.         LL x = Work(n, p[i]);  
  62.         LL y = Work(n - m, p[i]);  
  63.         LL z = Work(m, p[i]);  
  64.         x -= (y + z);  
  65.         ans *= quick_mod(p[i],x,P);  
  66.         ans %= P;  
  67.     }  
  68.     return ans;  
  69. }  
  70.   
  71. int main()  
  72. {  
  73.     int T;  
  74.     isprime();  
  75.     cin>>T;  
  76.     while(T--)  
  77.     {  
  78.         LL n,m,P;  
  79.         cin>>n>>m>>P;  
  80.         n += m - 2;  
  81.         m--;  
  82.         cout<<Solve(n,m,P)<<endl;  
  83.     }  
  84.     return 0;  
  85. }  


 

接下來看一些關於組合數取模的典型題目。

 

題目:http://acm.hdu.edu.cn/showproblem.php?pid=3944

 

分析:組合數取模的典型題目,用Lucas定理,注意要階乘預處理,否則會TLE的。

 

 

題目:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4536

 

題意:給一個集合,一共個元素,從中選取個元素,選出的元素中沒有相鄰的元素的選法一共有多少種?

 

分析:典型的隔板法,最終答案就是。然後用Lucas定理處理即可。

 

 

題目:http://acm.hdu.edu.cn/showproblem.php?pid=4373

 

題意:for循環嵌套,有兩種形式,第一類從1開始到,第二類從上一層循環當前數開始到,第一層一定

     是第一種類型,求總的循環的次數對364875103取餘的結果。

 

分析:首先可以看出,每一個第一類循環都是一個新的開始,與前面的狀態無關,所以可以把個嵌套分爲幾個不

     同的部分,每一個部分由第一類循環開始,最終結果相乘即可。剩下的就是第二類循環的問題,假設一個

     層循環,最大到,分析一下得到如下結果

    

     (1)只有一層,則循環次數爲

 

     (2)只有前兩層,則循環次數爲

 

         

 

     (3)只有前三層,則循環次數爲

 

         

 

      由此得到結論:第的循環次數爲

 

代碼:

  1. #include <iostream>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4.   
  5. using namespace std;  
  6. typedef long long LL;  
  7.   
  8. const int N = 25;  
  9. const int MOD1 = 97;  
  10. const int MOD2 = 3761599;  
  11. const int MOD = MOD1 * MOD2;  
  12.   
  13. int m,n,k;  
  14. int a[N];  
  15. LL fac1[MOD1+10];  
  16. LL fac2[MOD2+10];  
  17. LL inv1,inv2;  
  18.   
  19. LL quick_mod(LL a,LL b,LL m)  
  20. {  
  21.     LL ans = 1;  
  22.     a %= m;  
  23.     while(b)  
  24.     {  
  25.         if(b & 1)  
  26.         {  
  27.             ans = ans * a % m;  
  28.             b--;  
  29.         }  
  30.         b >>= 1;  
  31.         a = a * a % m;  
  32.     }  
  33.     return ans;  
  34. }  
  35.   
  36. LL C(LL n,LL m,LL p,LL fac[])  
  37. {  
  38.     if(n < m) return 0;  
  39.     return fac[n] * quick_mod(fac[m] * fac[n-m], p - 2, p) % p;  
  40. }  
  41.   
  42. LL Lucas(LL n,LL m,LL p,LL fac[])  
  43. {  
  44.     if(m == 0) return 1;  
  45.     return C(n % p, m % p, p, fac) * Lucas(n / p, m / p, p, fac);  
  46. }  
  47.   
  48. void Init()  
  49. {  
  50.     fac1[0] = fac2[0] = 1;  
  51.     for(int i=1; i<MOD1; i++)  
  52.         fac1[i] = (fac1[i-1] * i) % MOD1;  
  53.     for(int i=1; i<MOD2; i++)  
  54.         fac2[i] = (fac2[i-1] * i) % MOD2;  
  55.     inv1 = MOD2 * quick_mod(MOD2, MOD1-2, MOD1);  
  56.     inv2 = MOD1 * quick_mod(MOD1, MOD2-2, MOD2);  
  57. }  
  58.   
  59. int main()  
  60. {  
  61.     Init();  
  62.     int T, tt = 1;  
  63.     scanf("%d",&T);  
  64.     while(T--)  
  65.     {  
  66.         scanf("%d%d%d",&n,&m,&k);  
  67.         for(int i=0; i<k; i++)  
  68.             scanf("%d",&a[i]);  
  69.         a[k] = m;  
  70.         LL ans = 1;  
  71.         for(int i=0; i<k; i++)  
  72.         {  
  73.             LL m1 = Lucas(a[i+1] - a[i] + n - 1, a[i+1] - a[i], MOD1, fac1);  
  74.             LL m2 = Lucas(a[i+1] - a[i] + n - 1, a[i+1] - a[i], MOD2, fac2);  
  75.             LL mm = (m1 * inv1 + m2 * inv2) % MOD;  
  76.             ans = ans * mm % MOD;  
  77.         }  
  78.         printf("Case #%d: ",tt++);  
  79.         cout<<ans<<endl;  
  80.     }  
  81.     return 0;  
  82. }  


 

題目:http://acm.hdu.edu.cn/showproblem.php?pid=4349

 

題意:中有多少個奇數,其中

 

分析:其實組合數判斷奇偶性有一個優美的結論

          

            如果,那麼爲奇數,否則爲偶數

 

            當然本題要判斷的組合數很多,所以不能用上述結論,只能另闢蹊徑。由於是判斷奇偶性,那麼就是判斷

     是否爲1,利用Lucas定理,先把化爲二進制,這樣它們都是01序列了。我們又知道

     。這樣中爲0的地方對應的中的位置只有一種可能,那就是0

 

      這樣我們可以不用管中爲0的地方,只考慮中爲1的位置,可以看出,中爲1的位置對應的中爲0

      或1,其結果都是1,這樣答案就是:1<<(二進制表示中1的個數)

 

代碼:

  1. #include <iostream>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4.   
  5. using namespace std;  
  6.   
  7. int main()  
  8. {  
  9.     int n;  
  10.     while(scanf("%d",&n)!=EOF)  
  11.     {  
  12.         int cnt = 0;  
  13.         while (n)  
  14.         {  
  15.             if (n & 1) cnt++;  
  16.             n >>= 1;  
  17.         }  
  18.         printf("%d\n",1<<cnt);  
  19.     }  
  20.     return 0;  
  21. }  

 

題目:http://61.187.179.132/JudgeOnline/problem.php?id=1951

 

題意:給定兩個正整數,其中,求下面表達式的值

 

     

 

分析:由於999911659是素數,用費馬小定理降冪得到

 

     

 

     現在關鍵是求

    

     

 

     那麼我們枚舉分別計算,但是模的是合數,所以對999911658進行分解得到

 

     ,那麼分別求,即

 

      

 

     然後進一步得到同餘方程組爲

 

       

 

     再通過中國剩餘定理(CRT)可以求得最終答案

 

代碼:

  1. #include <iostream>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4.   
  5. using namespace std;  
  6. typedef long long LL;  
  7.   
  8. const int P = 999911659;  
  9.   
  10. LL a[5] = {0, 0, 0, 0};  
  11. LL m[5] = {2, 3, 4679, 35617};  
  12. LL fac[5][36010];  
  13. LL N, G;  
  14.   
  15. void Init()  
  16. {  
  17.     for(int i=0; i<4; i++)  
  18.     {  
  19.         fac[i][0] = 1;  
  20.         for(int j=1; j<36010; j++)  
  21.             fac[i][j] = fac[i][j-1] * j % m[i];  
  22.     }  
  23. }  
  24.   
  25. LL quick_mod(LL a,LL b,LL m)  
  26. {  
  27.     LL ans = 1;  
  28.     a %= m;  
  29.     while(b)  
  30.     {  
  31.         if(b & 1)  
  32.         {  
  33.             ans = ans * a % m;  
  34.             b--;  
  35.         }  
  36.         b >>= 1;  
  37.         a = a * a % m;  
  38.     }  
  39.     return ans;  
  40. }  
  41.   
  42. LL C(LL n,LL k,int cur)  
  43. {  
  44.     LL p = m[cur];  
  45.     if(k > n) return 0;  
  46.     return fac[cur][n] * quick_mod(fac[cur][k] * fac[cur][n-k], p - 2, p) % p;  
  47. }  
  48.   
  49. LL Lucas(LL n,LL k,int cur)  
  50. {  
  51.     LL p = m[cur];  
  52.     if(k == 0)  return 1;  
  53.     return C(n % p, k % p, cur) * Lucas(n / p, k / p, cur) % p;  
  54. }  
  55.   
  56. void extend_Euclid(LL a, LL b, LL &x, LL &y)  
  57. {  
  58.     if(b == 0)  
  59.     {  
  60.         x = 1;  
  61.         y = 0;  
  62.         return;  
  63.     }  
  64.     extend_Euclid(b, a % b,x, y);  
  65.     LL tmp = x;  
  66.     x = y;  
  67.     y = tmp - a / b * y;  
  68. }  
  69.   
  70. LL RemindChina(LL a[],LL m[],int k)  
  71. {  
  72.     LL M = 1;  
  73.     LL ans = 0;  
  74.     for(int i=0; i<k; i++)  
  75.         M *= m[i];  
  76.     for(int i=0; i<k; i++)  
  77.     {  
  78.         LL x, y;  
  79.         LL Mi = M / m[i];  
  80.         extend_Euclid(Mi, m[i], x, y);  
  81.         ans = (ans + Mi * x * a[i]) % M;  
  82.     }  
  83.     if(ans < 0)  
  84.         ans += M;  
  85.     return ans;  
  86. }  
  87.   
  88. int main()  
  89. {  
  90.     Init();  
  91.     while(cin>>N>>G)  
  92.     {  
  93.         a[0] = a[1] = 0;  
  94.         a[2] = a[3] = 0;  
  95.         if(G == P)  
  96.         {  
  97.             cout<<"0"<<endl;  
  98.             continue;  
  99.         }  
  100.         G %= P;  
  101.         for(int i=1; i*i <= N; i++)  
  102.         {  
  103.             if(N % i == 0)  
  104.             {  
  105.                 LL x = i;  
  106.                 a[0] = (a[0] + Lucas(N, x, 0)) % m[0];  
  107.                 a[1] = (a[1] + Lucas(N, x, 1)) % m[1];  
  108.                 a[2] = (a[2] + Lucas(N, x, 2)) % m[2];  
  109.                 a[3] = (a[3] + Lucas(N, x, 3)) % m[3];  
  110.                 x = N / i;  
  111.                 if(i * i != N)  
  112.                 {  
  113.                     a[0] = (a[0] + Lucas(N, x, 0)) % m[0];  
  114.                     a[1] = (a[1] + Lucas(N, x, 1)) % m[1];  
  115.                     a[2] = (a[2] + Lucas(N, x, 2)) % m[2];  
  116.                     a[3] = (a[3] + Lucas(N, x, 3)) % m[3];  
  117.                 }  
  118.             }  
  119.         }  
  120.         LL ans = quick_mod(G, RemindChina(a, m, 4), P);  
  121.         cout<<ans<<endl;  
  122.     }  
  123.     return 0;  
  124. }  


 

題目:已知有如下表達式

 

     

 

      給定,求

 

分析:如果直接二項式展開,這樣會很麻煩,而且不容易求出,本題有技巧。做如下變換

 

     

  

     所以問題變爲求的值。     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章