YbtOJ 「数学基础」 第4章 组合数学

组合数

加法原理

分类计数原理 比如说做一件事有n类方法 其中每一类有Mi种方法 那么最后的方法数就是i=1nMi

乘法原理

分步计数原理 比如说做一件事分n步 其中每一步有Mi种方法 那么最后的方法数就是i=1nMi

排列数

n个数中任意取m个元素 按照一定的顺序排一列 叫做从n个数中取出m个数的一个排列 这样的不同排列的个数就是排列数

计算公式:Anm=n!(nm)!

全排列:Ann=n!0!=n!

组合数

n个元素中取出m个元素 组成一个集合 叫做从n个数中取出m个元素的个数 那么组合数的计算公式就是Cnm=Anmm!=n!m!(nm)!

特别地 当m>n Cnm=Anm=0

求组合数

递推组合数 Cnm=Cn1m+Cn1m1

时间复杂度O(n2)

O(1)组合数见例2

二项式定理

对于一个形如(ax+by)k的柿子展开后的xnym项 推得Ans=anbmC[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=((m1)!)1×m1 而且(1!)1=112开始递推即可

O(1)组合数

计算公式为Cnm=n!m!(nm)!

那么将计算公式下方的两个东西用逆元乘起来

int C ( int n , int m )
{
	if ( n < m ) return 0;
	return fac[n] % mod * inv[m] % mod * inv[n-m] % mod;
}

lucas定理

Cnm=Cn/pm/pCn mod pm mod pmod 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
  • 那个朝代流传的猪文文字恰好为远古时期的1k,其中kn的一个正约数(可以是1n )。(枚举 约数)
  • 然而从n个文字中保留下nk个的情况也是相当多的。(即组合数)
  • 所有可能的k的所有情况数加起来为p的话,那么他研究古代文字的代价将会是gp

可以得到题目真正需要求的柿子 (枚举n/kk是一样的,因为你真正n/k的情况可以在k的情况下计算,也为了好看) :

gk|nCnkmod999911659


Case1:当999911659g时,Ans=0 (一定要判! )

Case2:如下

一个前置结论:

abab mod (p1)(mod p)

证明:首先由费马小定理得ap11(mod p)

(ap1)k1(mod p)(同余的同幂性)

我们设b=k(p1)+r 也就是r=b mod (p1)

所以ab=(ap1)kar(mod p)

所以abar(mod p)

abab mod(p1)

由上面结论可以得出答案就是ans=gk|nCnkmod999911659=gk|nCnkmod999911658mod999911659

即求:k|nCnkmod999911658

我们如果对一坨柿子用lucas的话就寄了(因为p不是质数)

所以我们对于999911658分解质因数 999911658=2×3×4679×35617(记为p1p4)

进而可以得出四个同余方程:

{S=ans1(mod p1)S=ans2(mod p2)S=ans3(mod p3)S=ans4(mod p4)

所以我们首先对于四个模数分别求出k|nCnkmodpi 的值分别为ansi(其中组合数使用lucas定理)

其次要找到一个S来满足四个同余方程组 用CRT求解即可

之后快速幂求gSmod 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 @   Echo_Long  阅读(88)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示