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;
}
posted @ 2023-06-29 21:10  Echo_Long  阅读(119)  评论(0)    收藏  举报