从逆元到组合数(模板)

​​​​​​1.组合数:

众所周知C_{a}^{b} = \tfrac{a!}{b!*(a-b)!},我们一般要计算C_{a}^{b} % p,但当a、b过大时计算机就无法单独计算出分子和分母然后再除,这是我们就要引出逆元的概念.

2.逆元

百度:逆元素是指一个可以取消另一给定元素运算的元素。

也就是说逆元可以取消它对应元素的对应运算。

所以如果我们要求\tfrac{a!}{b!*(a-b)!}% p就是算:

a! * b!的逆元 * (a - b)!的逆元 % p

3.逆元求法

(1)费马小定理:

ll fmx(ll x,ll power,mod)//a ^ (p - 1) = 1 (mod p)	//x为(mod p)下 a的逆元的定义为 ax = 1 (mod p) 
{						//p为素数且a,p互质时,左式成立(费马小定理)
	x %= mod;           //即ax = a^(p - 1) (mod p)
	ll ans = 1;			//因此x = a^(p - 2) (mod p)
	for(;power;power >>= 1,(x *= x) %= mod)//快速幂
		if(power & 1) 
		(ans *= x) %= mod;
	return ans;
}
int main()
{
	ll x = fmx(a,p - 2,p);//x为a在mod p意义下的逆元
}

(2)线性算法:

void pre_ny()
{
    ny[1] = 1;
    for(int i = 2; i <= MAXN; ++ i)
        ny[i] = (p - p / i) * ny[p % i] % p;
}

你问为什么,我也不知道,推出来就是这样的,模板,抄就完了

(3)阶乘逆元

众所周知n! = (n - 1)! * n

所以 n!的逆元= (n - 1)!的逆元 * n的逆元 % p (mod p)

而0! = 1! = 1,1的逆元为1,你就能递推阶乘逆元了:

//先线性算法初始化需要的逆元ny
pre_jcny()
{
    cjny[0] = cjny[1] = 1;
    for(int i = 2;i <= n;i++)
    {
	    cjny[i] = (cjny[i - 1] * ny[i]) % p;
    }
}

 4.结合模板

typedef long long ll;
const ll MAXN = 1000001;
ll ny[MAXN],p = 998244353;
ll jcny[MAXN],jc[MAXN];
void pre_ny()//预处理逆元
{
    ny[1] = 1;
    for(ll i = 2;i <= MAXN;i++)
        ny[i] = ((p - p / i) * ny[p % i]) % p;
}
void pre_jcny()//预处理阶乘的逆元
{
    jcny [0] = jcny[1] = 1;
    for(ll i = 2;i <= MAXN;i++)
        jcny[i] = jcny[i - 1] * ny[i] % p;
}
void pre_jc()//预处理阶乘
{
    jc[0] = jc[1] = 1;
    for(ll i = 2;i <= MAXN;i++)
        jc[i] = jc[i - 1] * i % p;
}
ll C(ll a,ll b)//计算组合数
{
    return (jc[a] * jcny[a - b] % p * jcny[b] % p) % p;
}
int main()
{
    pre_ny();pre_jcny();pre_jc();
}

从逆元到组合数(模板)_第1张图片

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