求解组合数取模---拓展欧几里德和费马小定理求解逆元

组合数:C(n, m) ;         组合数取模:C(n, m) % mod,mod是一个很大的数。

1.公式:

求解组合数取模---拓展欧几里德和费马小定理求解逆元_第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
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的矩阵:

求解组合数取模---拓展欧几里德和费马小定理求解逆元_第2张图片


当数很大时,显然复杂度太大,所以这里使用拓展欧几里得和费马小定理的方法求解。

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上的求解组合数取模之类的问题应用是否广泛,此外这里还用到了求解最大公约数(欧几里得辗转相除法)以及快速幂的方法,这类的基本算法应当十分熟悉。


以上。欢迎您提出宝贵的意见,让我们一同进步。谢谢!

你可能感兴趣的:(算法,c/c++)