阶乘逆元线性求逆元组合计数牛妹的数学难题
Posted 行码棋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阶乘逆元线性求逆元组合计数牛妹的数学难题相关的知识,希望对你有一定的参考价值。
⭐️前置知识⭐️
1️⃣逆元简介
a
×
b
≡
1
(
m
o
d
p
)
a \\times b \\equiv 1 ( mod\\,\\,p)
a×b≡1(modp),可以称a
是b
在模p
情况下的逆元.
逆元其实就是可以看作倒数
2️⃣阶乘逆元
方式一:
通过费马小定理求逆元:
当p
为素数,并且gcd(a,p)=1
时,我们有
a
p
−
1
≡
1
(
m
o
d
p
)
a^p−1≡1(mod\\ p)
ap−1≡1(mod p)。那么就有
a
p
−
2
×
a
≡
1
(
m
o
d
p
)
a^p−2×a≡1(mod\\ p)
ap−2×a≡1(mod p),则a
的逆元就是
a
p
−
2
a^p−2
ap−2
下面ksm
函数为快速幂
fact[0] = 1;
for(int i = 1; i < N; i++)
fact[i] = fact[i - 1] * i % mod;
inv[i] = ksm(fact[i], mod - 2);
方式二:
通过式子 1 ( n + 1 ) ! × ( n + 1 ) = 1 n ! \\frac1(n+1)!\\times (n+1)=\\frac1n! (n+1)!1×(n+1)=n!1倒推接近线性求阶乘逆元
1 ( n + 1 ) ! \\frac1(n+1)! (n+1)!1其实就可以看作 ( n + 1 ) ! (n+1)! (n+1)!的逆元
for(int i = 1; i <= n; i++)
fact[i] = fact[i - 1] * i % mod;
inv[n] = ksm(fact[n], mod - 2);
for(int i = n - 1; i >= 1; i--)
inv[i] = inv[i + 1] * (i + 1) % mod;
3️⃣线性求逆元
求
[
1
,
N
−
1
]
[1,N-1]
[1,N−1]关于mod
的逆元时,可以做到在
O
(
N
)
O(N)
O(N)时间内解决
设模数为p
对于当前的i
,设
p
=
k
×
i
+
r
p=k×i+r
p=k×i+r,则
k
×
i
+
r
≡
0
(
m
o
d
p
)
k
×
i
×
(
i
−
1
×
r
−
1
)
+
r
×
(
i
−
1
×
r
−
1
)
≡
0
(
m
o
d
p
)
k
×
r
−
1
+
i
−
1
≡
0
(
m
o
d
p
)
i
−
1
≡
−
k
×
r
−
1
(
m
o
d
p
)
i
−
1
≡
−
⌊
p
i
⌋
×
r
−
1
(
m
o
d
p
)
\\beginaligned k \\times i + r & \\equiv 0 &\\,\\,(mod \\,\\, p) \\\\ k \\times i \\times ( i^-1 \\times r ^-1) + r \\times (i^-1 \\times r^-1) &\\equiv 0 &\\,\\,( mod \\,\\, p) \\\\ k \\times r^-1 + i ^ -1 & \\equiv 0 &\\,\\, (mod \\,\\, p)\\\\ i^-1 & \\equiv -k \\times r^-1 &\\,\\, (mod \\,\\, p) \\\\ i^-1 & \\equiv - \\left \\lfloor \\fracpi\\right \\rfloor \\times r^-1 &\\,\\,(mod\\,\\,p) \\endaligned
k×i+rk×i×(i−1×r−1)+r×(i−1×r−1)k×r−1+i−1i−1i−1≡0≡0≡0≡−k×r−1≡−⌊ip⌋×r−1(modp)(modp)(modp)(modp)(modp)
注意:
i
n
v
[
1
]
inv[1]
inv[1]一定要初始化为1,需要从2开始递推,不能从1开始递推
inv[0] = inv[1] = 1;
for(int i = 2; i < N; i++)
inv[i] = inv[mod % i] * (mod - mod / i) % mod;
同时可以通过线性求逆元求阶乘逆元:
只需要再将逆元用类似阶乘的形式乘起来即可,求得的inv[i]
即为
i
!
i!
i!的逆元
for(int i = 2; i < N; i++)
inv[i] = inv[i - 1] * inv[i] % mod;
4️⃣组合数计算
C
n
m
C_n^m
Cnm计算
⭐️方式一:公式计算
计算都是在逆元或者阶乘基础上计算的
C
n
m
=
n
!
m
!
∗
(
n
−
m
)
!
C_n^m = \\fracn!m!*(n-m)!
Cnm=m!∗(n−m)!n!
ll C(ll n, ll m)
if(n < m) return 0;
return fact[n] * inv[m] % mod * inv[n - m] % mod;
⭐️方式二:递推方式
需要建表,所以如果计算范围比较大时需要的空间也大
递推公式 :
C
n
m
=
C
n
−
1
m
+
C
n
−
1
m
−
1
C_n^m = C_n-1^m + C_n-1^m-1
Cnm=Cn−1m+Cn−1m−1
for(int i = 1; i <= n; i++)
for(int j = 0; j <= i; j++)
if(i == j || j == 0) c[i][j] = 1;
else c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
5️⃣题目
链接:
https://ac.nowcoder.com/acm/contest/23481/J
就是在数组中选中k个值相乘,最后把结果相加即可
因为数组中的数大小只有三种情况。所以可以根据这个进行切入口。
首先
0
0
0可以不用考虑,接下考虑有
n
n
n个
1
1
1和
m
m
m个
2
2
2,如果上述和式中
1
1
1出现了
i
i
i个,那么
2
2
2需要出现
k
−
i
k-i
k−i个,于是答案为
∑
i
=
0
k
C
(
n
,
i
)
∗
C
(
m
,
k
−
i
)
∗
2
k
−
i
\\sum_i=0^kC(n,i)*C(m,k-i)*2^k-i
∑i=0kC(n,i 以上是关于阶乘逆元线性求逆元组合计数牛妹的数学难题的主要内容,如果未能解决你的问题,请参考以下文章