YbtOJ 「数学基础」 第4章 组合数学
组合数
加法原理
分类计数原理 比如说做一件事有\(n\)类方法 其中每一类有\(M_i\)种方法 那么最后的方法数就是\(\sum_{i=1}^nM_i\)
乘法原理
分步计数原理 比如说做一件事分\(n\)步 其中每一步有\(M_i\)种方法 那么最后的方法数就是\(\prod_{i=1}^nM_i\)
排列数
从\(n\)个数中任意取\(m\)个元素 按照一定的顺序排一列 叫做从\(n\)个数中取出\(m\)个数的一个排列 这样的不同排列的个数就是排列数
计算公式:\(A_n^m=\frac {n!}{(n-m)!}\)
全排列:\(A_n^n=\frac {n!}{0!}=n!\)
组合数
从\(n\)个元素中取出\(m\)个元素 组成一个集合 叫做从\(n\)个数中取出\(m\)个元素的个数 那么组合数的计算公式就是\(C_n^m=\frac {A_n^m} {m!}=\frac {n!}{m!(n-m)!}\)
特别地 当\(m>n\) \(C_n^m={A_n^m}=0\)
求组合数
递推组合数 \(C_n^m=C_{n-1}^m+C_{n-1}^{m-1}\)
时间复杂度\(O(n^2)\)
\(O(1)\)组合数见例2
二项式定理
对于一个形如\((ax+by)^k\)的柿子展开后的\(x^ny^m\)项 推得\(Ans=a^n b^m C[min(n,m)][k]\)
A. 【例题1】计算系数
直接用二项式定理即可
code:
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define mid ((l+r)>>1)
#define int long long
const int N = 1e3 + 5;
const int mod = 10007;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int c[N][N];//i个中选择j个
int C ( int n , int m )//n个中选出来m个
{
for ( int i = 0 ; i <= n ; i ++ )
for ( int j = 0 ; j <= i ; j ++ )
{
if ( !j ) c[i][j] = 1;
else c[i][j] = ( c[i-1][j] + c[i-1][j-1] ) % mod;
}
return c[n][m] % mod;
}
int ksm ( int x , int k )
{
int base = x , ans = 1;
while (k)
{
if ( k & 1 ) ans = ans * base % mod;
base = base * base % mod;
k >>= 1;
}
return ans;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int a = read() % mod , b = read() % mod , k = read() , n = read() , m = read();
cout << C ( k , min ( n , m ) ) % mod * ksm ( a , n ) % mod * ksm ( b , m ) % mod << endl;
return 0;
}
B. 【例题2】方案统计
预处理阶乘逆元
我们可以知道\((m!)^{-1}=((m-1)!)^{-1}\times m^{-1}\) 而且\((1!)^{-1}=1^{-1}\) 从\(2\)开始递推即可
\(O(1)\)组合数
计算公式为\(C_n^m=\frac {n!}{m!(n-m)!}\)
那么将计算公式下方的两个东西用逆元乘起来
int C ( int n , int m )
{
if ( n < m ) return 0;
return fac[n] % mod * inv[m] % mod * inv[n-m] % mod;
}
\(lucas\)定理
\(C_n^m =C_{n/p}^{m/p} ∗C_{n\ mod\ p}^{m\ mod\ p} mod\ p\)
int lucas ( int n , int m )
{
if ( m == 0 ) return 1;
return C ( n % mod , m % mod ) * lucas ( n / mod , m / mod ) % mod;
}
code:
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define mid ((l+r)>>1)
#define int long long
const int N = 2e6 + 5;
const int mod = 10007;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fac[N] , inv[N];
void init ()
{
fac[0] = inv[0] = fac[1] = inv[1] = 1;
for ( int i = 2 ; i <= mod ; i ++ )
inv[i] = ( ( mod - mod / i ) * inv[mod%i] + mod ) % mod , fac[i] = fac[i-1] * i % mod;
for ( int i = 2 ; i <= mod ; i ++ ) // 求阶乘逆元
inv[i] = inv[i-1] * inv[i] % mod;
}
int C ( int n , int m )
{
if ( n < m ) return 0;
return fac[n] % mod * inv[m] % mod * inv[n-m] % mod;
}
int lucas ( int n , int m )
{
if ( m == 0 ) return 1;
return C ( n % mod , m % mod ) * lucas ( n / mod , m / mod ) % mod;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
init();
for ( int i = 1 ; i <= T ; i ++ )
{
int n = read() , m = read();
cout << lucas ( n , m ) << endl;
}
return 0;
}
C. 【例题3】古代猪文
题意
- 远古时期猪文文字总个数为\(n\)。
- 那个朝代流传的猪文文字恰好为远古时期的\(\frac{1}{k}\),其中\(k\)是\(n\)的一个正约数(可以是\(1\)或\(n\) )。(枚举 约数)
- 然而从\(n\)个文字中保留下\(\frac{n}{k}\)个的情况也是相当多的。(即组合数)
- 所有可能的\(k\)的所有情况数加起来为\(p\)的话,那么他研究古代文字的代价将会是\(g^{p}\)。
可以得到题目真正需要求的柿子 (枚举\(n / k\)与\(k\)是一样的,因为你真正\(n / k\)的情况可以在\(k\)的情况下计算,也为了好看) :
\(g^{\sum_{k|n}C_{n}^{k} }\bmod 999911659\)
\(Case 1\):当\(999911659 \mid g\)时,\(Ans = 0\) (一定要判! )
$Case 2 $:如下
一个前置结论:
\(a^b\equiv a^{b\ mod\ (p-1)} (mod\ p)\)
证明:首先由费马小定理得$a^{p-1}\equiv1(mod\ p) $
则\((a^{p-1})^k\equiv 1(mod\ p)\)(同余的同幂性)
我们设\(b=k*(p-1)+r\) 也就是\(r=b\ mod\ (p-1)\)
所以\(a^b=(a^{p-1})^k*a^r(mod\ p)\)
所以\(a^b\equiv a^r(mod\ p)\)
即\(a^b\equiv a^{b\ mod (p-1)}\)
由上面结论可以得出答案就是\(ans=g^{\sum_{k|n}C_{n}^{k}} \bmod 999911659=g^{\sum_{k|n}C_{n}^{k} \bmod 999911658}mod999911659\)
即求:\(\sum_{k|n} \mathrm{C}_{n}^{k} \bmod 999911658\)
我们如果对一坨柿子用\(lucas\)的话就寄了(因为\(p\)不是质数)
所以我们对于\(999911658\)分解质因数 \(999911658 = 2 \times 3 \times 4679 \times 35617\)(记为\(p_1-p_4\))
进而可以得出四个同余方程:
\(\begin{cases} S=ans_1(mod\ p_1)\\ S=ans_2(mod\ p_2)\\S=ans_3(mod\ p_3)\\S=ans_4(mod\ p_4)\\\end{cases}\)
所以我们首先对于四个模数分别求出\(\sum_{k|n}\mathrm{C}_{n}^{k} \bmod p_i\) 的值分别为\(ans_i\)(其中组合数使用\(lucas\)定理)
其次要找到一个\(S\)来满足四个同余方程组 用\(CRT\)求解即可
之后快速幂求\(g^Smod\ 999911659\)的值即可
code:
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int mod = 999911658;
const int N = 1e6 + 5;
const int b[5] = { 0 , 2 , 3 , 4679 , 35617 };
inl int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int nn , G , res[5] , S , fac[N] , inv[N];
int ksm ( int x , int k , int p )
{
int base = x , ans = 1;
while (k)
{
if ( k & 1 ) ans = ans * base % p;
base = base * base % p;
k >>= 1;
}
return ans;
}
void init ( int p )
{
fac[0] = fac[1] = inv[0] = inv[1] = 1;
for ( int i = 2 ; i <= p ; i ++ )
fac[i] = fac[i-1] * i % p , inv[i] = ( ( p - p / i ) * inv[p%i] % p + p ) % p;
for ( int i = 2 ; i <= p ; i ++ ) inv[i] = inv[i-1] * inv[i] % p;
}
int C ( int n , int m , int p )
{
if ( m > n ) return 0;
return fac[n] % p * inv[m] % p * inv[n-m] % p;
}
int lucas ( int n , int m , int p )
{
if ( m == 0 ) return 1;
return C ( n % p , m % p , p ) * lucas ( n / p , m / p , p ) % p;
}
int invv ( int x , int p ) { return ksm ( x , p - 2 , p ); }
void CRT ()
{
for ( int i = 1 ; i <= 4 ; i ++ )
S = ( S + res[i] * ( mod / b[i] ) % mod * invv ( mod / b[i] , b[i] ) % mod ) % mod;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
nn = read() , G = read();
if ( G % ( mod + 1 ) == 0 ) { cout << 0 << endl; return 0; }
for ( int i = 1 ; i <= 4 ; i ++ )
{
init(b[i]);
for ( int k = 1 ; k * k <= nn ; k ++ )
if ( nn % k == 0 )
{
res[i] = ( res[i] + lucas ( nn , k , b[i] ) ) % b[i];
if ( k * k != nn ) res[i] = ( res[i] + lucas ( nn , nn / k , b[i] ) ) % b[i] ;
}
}
CRT();
cout << ksm ( G , S , mod + 1 ) << endl;
return 0;
}

浙公网安备 33010602011771号