组合数:C(n, m) ; 组合数取模:C(n, m) % mod,mod是一个很大的数。
1.公式:
2.性质:(1)C(n,m)= C(n,n-m) 其中有C(n, 0) = 1;
(2)C(n,m)=C(n-1,m-1)+C(n-1,m)。可以用作递归中的公式。
性质2和杨辉三角的相似性(核心部分就是利用了性质2)。
例题:打印杨辉三角:
#include<iostream>
using namespace std;
int a[100][100];
int main(){
int n;
while(cin >> n){
a[0][0] = 1; a[0][1] = 1;
for(int i = 1; i < n - 1; i ++){
for(int j = 0; j < i + 2; j ++){
a[i][j] = a[i - 1][j - 1] + a[i - 1][j];
}
}
for(int i = 0; i < n - 1; i ++){
for(int j = 0; j < i + 1; j ++){
cout << a[i][j] << " ";
}
cout << a[i][i + 1] << endl;
}
}
return 0;
}
打印前20的矩阵:
当数很大时,显然复杂度太大,所以这里使用拓展欧几里得和费马小定理的方法求解。
1、拓展欧几里德:(gcd就是求最大公约数)
给定模数m,求a的逆相当于求解ax=1(mod m)
这个方程可以转化为ax-my=1
然后套用求二元一次方程的方法,用扩展欧几里得算法求得一组x0,y0和gcd
检查gcd是否为1
gcd不为1则说明逆元不存在
若为1,则调整x0到0~m-1的范围中即可。
//拓展欧几里得算法求逆元
void exgcd(ll a, ll b, ll &x, ll &y) {
if(!b){
x = 1;
y = 0;
}
else{
exgcd(b, a % b, y, x);
y -= (a / b) * x;
}
}
ll inv(ll a, ll n) {
ll x, y;
exgcd(a, n, x, y);
return (x + n) % n;
}
2、费马小定理:
在模为素数p的情况下,有费马小定理
a^(p-1)=1(mod p)
那么a^(p-2)=a^-1(mod p)
也就是说a的逆元为a^(p-2)。
费马小定理求逆元
ll pow(ll a, ll n, ll p) //快速幂 a^n % p
{
ll ans = 1;
while(n)
{
if(n & 1) ans = ans * a % p;
a = a * a % p;
n >>= 1;
}
return ans;
}
ll inv(ll a, ll b) //费马小定理求逆元
{
return pow(a, b - 2, b);
}
这里的两个定理在很多oj上的求解组合数取模之类的问题应用是否广泛,此外这里还用到了求解最大公约数(欧几里得辗转相除法)以及快速幂的方法,这类的基本算法应当十分熟悉。
以上。欢迎您提出宝贵的意见,让我们一同进步。谢谢!