递推法 杨辉三角
递推式Cnm=Cn−1m+Cn−1m−1C_{n}^{m} = C_{n-1}^{m} + C_{n-1}^{m-1}Cnm=Cn−1m+Cn−1m−1
从 n个不同的数中选出 m个的方案数是 CnmC_{n}^{m}Cnm
对第 1 个数有 选或不选 两种决策: CnmC_{n}^{m}Cnm
若不选,则从剩下的 n−1个中选 m 个,即 ; Cn−1mC_{n-1}^{m}Cn−1m
若选,则从剩下的 n−1个中选 m−1 个,即。Cn−1m−1C_{n-1}^{m-1}Cn−1m−1
加法原理Cnm=Cn−1m+Cn−1m−1C_{n}^{m} = C_{n-1}^{m} + C_{n-1}^{m-1}Cnm=Cn−1m+Cn−1m−1
void comb()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<=i;j++)
{
if(j==0)C[i][j] = 1;
else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
}
}
快速幂+逆元
组合数计算优化方案
递推法 Cnm=Cn−1m+Cn−1m−1C_n^m = C_{n-1}^m + C_{n-1}^{m-1}Cnm=Cn−1m+Cn−1m−1,会 TLE。
考虑用 Cnm=n!(n−m)!⋅m!C_n^m = \dfrac{n!}{(n - m)! \cdot m!}Cnm=(n−m)!⋅m!n! 直接计算。
开两个数组分别存模意义下的阶乘和阶乘的逆元:
- 用 f[x]f[x]f[x] 存 x! mod px! \bmod px!modp 的值;
- 用 g[x]g[x]g[x] 存 (x!)−1 mod p(x!)^{-1} \bmod p(x!)−1modp 的值。
因为 ppp 是质数且 n,mn, mn,m 都小于 ppp,即 n,mn, mn,m 与 ppp 互质,
所以根据费马小定理 a⋅ap−2≡1(modp)a \cdot a^{p - 2} \equiv 1 \pmod pa⋅ap−2≡1(modp),
因为a⋅a−1≡1(modp)a \cdot a^{-1} \equiv 1 \pmod pa⋅a−1≡1(modp)
所以 a−1≡ap−2(modp)a^{-1} \equiv a^{p-2} \pmod pa−1≡ap−2(modp)
可以用快速幂求逆元。
无法直接计算 n!(n−m)!⋅m! mod p\dfrac{n!}{(n - m)! \cdot m!}\bmod p(n−m)!⋅m!n!modp,所以转化为 n!⋅((n−m)!⋅m!)−1 mod pn!\cdot((n-m)!\cdot m!)^{-1} \bmod pn!⋅((n−m)!⋅m!)−1modp。
所以Cnm mod p=f[n]×g[n−m]×g[m] mod pC_n^m \bmod p = f[n] \times g[n - m] \times g[m] \bmod pCnmmodp=f[n]×g[n−m]×g[m]modp。
typedef long long ll;
ll qpow(ll a,int b,ll p)
{
ll res;
while(b)
{
if(a & 1)res=res*a%p;
a=a*a%p;
b >>= 1;
}
return res;
}
void init()
{
f[0]=1,g[0]=1;
for(int i=1;i<n;i++)
{
f[i] = i*f[i-1]%p;
g[i] = g[i-1]*qpow(i,p-2)%p;
}
}
ll comb(ll n,ll m)
{
return f[n]*g[n-m]%p*g[m]%p;
}
卢卡斯定理
卢卡斯定理:
Cnm≡Cn/pm/p⋅Cn mod pm mod p(modp)C_n^m \equiv C_{n/p}^{m/p} \cdot C_{n \bmod p}^{m \bmod p} \pmod{p}Cnm≡Cn/pm/p⋅Cnmodpmmodp(modp)
其中 ppp 为质数。
n mod pn \bmod pnmodp 和 m mod pm \bmod pmmodp 一定是小于 ppp 的数,可以直接求解,
Cn/pm/pC_{n/p}^{m/p}Cn/pm/p 可以继续用 Lucas 定理求解。
边界条件:当 m=0m = 0m=0 时,返回 111。
typedef long long ll;
ll qpow(ll a,int b,ll p)
{
ll res;
while(b)
{
if(a & 1)res=res*a%p;
a=a*a%p;
b >>= 1;
}
return res;
}
void init(ll N,ll p)
{
f[0]=1,g[0]=1;
for(int i=1;i<N;i++)
{
f[i] = i*f[i-1]%p;
g[i] = g[i-1]*qpow(i,p-2)%p;
}
}
ll comb(ll n,ll m,ll p)
{
return f[n]*g[n-m]%p*g[m]%p;
}
ll lucas(ll n,ll m)
{
if(m==0)return 1;
return lucas(n/p,m/p)*comb(n%p,m%p)%p;
}