1.利用递推式预处理组合数取模
题目描述
给定
n
n
n 组询问,每组询问给定两个整数
a
,
b
a, b
a,b,请你输出
C
a
b
m
o
d
(
1
0
9
+
7
)
C_a^b \ mod \ (10^9+7)
Cab mod (109+7) 的值。
数据范围
1
≤
n
≤
10000
1 \le n \le 10000
1≤n≤10000
1
≤
b
≤
a
≤
2000
1 \le b \le a \le 2000
1≤b≤a≤2000
递推式
C
a
b
=
C
a
−
1
b
+
C
a
−
1
b
−
1
C_a^b = C_{a-1}^{b}+C_{a-1}^{b-1}
Cab=Ca−1b+Ca−1b−1
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 2010, mod = 1e9 + 7;
int c[N][N];
void init()
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j <= i; j++)
{
if (!j)
{
c[i][j] = 1;
}
else
{
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
}
}
}
int main()
{
init();
int n;
scanf("%d", &n);
while (n--)
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", c[a][b]);
}
return 0;
}
2.预处理阶乘的余数和阶乘逆元的余数
题目描述
给定
n
n
n 组询问,每组询问给定两个整数
a
,
b
a, b
a,b,请你输出
C
a
b
m
o
d
(
1
0
9
+
7
)
C_a^b\ mod\ (10^9+7)
Cab mod (109+7) 的值。
数据范围
1
≤
n
≤
10000
1 \le n \le 10000
1≤n≤10000
1
≤
b
≤
a
≤
1
0
5
1 \le b \le a \le 10^5
1≤b≤a≤105
逆元
若
a
∗
x
≡
1
(
m
o
d
b
)
a ∗ x ≡ 1 \ (mod \ b)
a∗x≡1 (mod b) 且
a
a
a 与
b
b
b 互质,那么我们就能定义
x
x
x 为
a
a
a 的逆元,记为
a
−
1
a^{−1}
a−1,所以我们也可以称
x
x
x 为
a
a
a 在
m
o
d
b
mod\ b
mod b 意义下的倒数。
因此,对于
a
b
(
m
o
d
p
)
\frac{a}{b}\ (mod\ p)
ba (mod p),我们就可以求出
b
b
b 在
m
o
d
p
mod\ p
mod p 下的逆元,然后乘上
a
a
a,再
m
o
d
p
mod\ p
mod p,就是这个分数的值了。
快速幂求逆元
这个做法要利用费马小定理,如下:
若
p
p
p 为素数,
a
a
a 为正整数,且
a
a
a、
p
p
p 互质,则有
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p−1} ≡ 1 \ (mod \ p)
ap−1≡1 (mod p)。
可以发现这个式子右边刚好为
1
1
1,因此,代入原式即可得到:
a
∗
x
≡
1
(
m
o
d
p
)
a ∗ x ≡ 1\ (mod\ p)
a∗x≡1 (mod p)
a
∗
x
≡
a
p
−
1
(
m
o
d
p
)
a ∗ x ≡ a^{p−1}\ (mod\ p)
a∗x≡ap−1 (mod p)
x
≡
a
p
−
2
(
m
o
d
p
)
x ≡ a^{p−2}\ (mod\ p)
x≡ap−2 (mod p)
所以我们可以用快速幂来算出
a
p
−
2
(
m
o
d
p
)
a^{p−2}\ (mod\ p)
ap−2 (mod p) 的值,这个数就是它的逆元了。
组合数
C
a
b
=
a
∗
(
a
−
1
)
∗
.
.
.
∗
(
a
−
b
+
1
)
b
∗
(
b
−
1
)
∗
.
.
.
∗
1
=
a
!
b
!
∗
(
a
−
b
)
!
C_a^b = \frac{a*(a-1)*...*(a-b+1)}{b*(b-1)*...*1} = \frac{a!}{b!*(a-b)!}
Cab=b∗(b−1)∗...∗1a∗(a−1)∗...∗(a−b+1)=b!∗(a−b)!a!
首先,预处理阶乘的余数和阶乘逆元的余数:
f
a
c
t
[
i
]
=
(
i
!
)
m
o
d
(
1
0
9
+
7
)
fact[i] = (i!)\ mod \ (10^9+7)
fact[i]=(i!) mod (109+7)
i
n
f
a
c
t
[
i
]
=
(
i
!
)
−
1
m
o
d
(
1
0
9
+
7
)
infact[i] = (i!)^{-1}\ mod\ (10^9+7)
infact[i]=(i!)−1 mod (109+7)
那么,组合数求解如下:
C
a
b
=
f
a
c
t
[
a
]
∗
i
n
f
a
c
t
[
b
]
∗
i
n
f
a
c
t
[
a
−
b
]
C_a^{b} = fact[a]*infact[b]*infact[a-b]
Cab=fact[a]∗infact[b]∗infact[a−b]
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 100010, mod = 1e9 + 7;
int fact[N], infact[N];
int qmi(int a, int b, int m)
{
int res = 1;
while (b)
{
if (b & 1) res = (ll)res * a % mod;
a = (ll)a * a % mod;
b >>= 1;
}
return res;
}
int main()
{
// 预处理阶乘的余数和阶乘逆元的余数
fact[0] = 1, infact[0] = 1;
for (int i = 1; i < N; i++)
{
fact[i] = (ll)fact[i - 1] * i % mod;
infact[i] = (ll)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
int n;
scanf("%d", &n);
while (n--)
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", (ll)fact[a] * infact[b] % mod * infact[a - b] % mod);
}
return 0;
}
3.卢卡斯定理
题目描述
给定
n
n
n 组询问,每组询问给定三个整数
a
,
b
,
p
a,b,p
a,b,p,其中
p
p
p 是质数,请你输出
C
a
b
m
o
d
p
C_a^b\ mod\ p
Cab mod p 的值。
数据范围
1
≤
n
≤
20
1 \le n \le 20
1≤n≤20
1
≤
b
≤
a
≤
1
0
18
1 \le b \le a \le 10^{18}
1≤b≤a≤1018
1
≤
p
≤
1
0
5
1 \le p \le 10^5
1≤p≤105
Lucas定理
若
p
p
p 是质数,则对于任意整数
1
≤
m
≤
n
1 \leq m \leq n
1≤m≤n 有
C
n
m
≡
C
n
m
o
d
p
m
m
o
d
p
∗
C
n
/
p
m
/
p
(
m
o
d
p
)
C_n^m \equiv C_{n\ mod\ p}^{m\ mod \ p}*C_{n/p}^{m/p}\ (mod\ p)
Cnm≡Cn mod pm mod p∗Cn/pm/p (mod p)。
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
// 快速幂模板
int qmi(int a, int b, int p)
{
int res = 1;
while (b)
{
if (b & 1) res = (ll)res * a % p;
a = (ll)a * a % p;
b >>= 1;
}
return res;
}
// 通过定义求组合数C(a, b)
int C(int a, int b, int p)
{
if (b > a) return 0;
int res = 1;
for (int i = 1, j = a; i <= b; i++, j--)
{
res = (ll)res * j % p;
res = (ll)res * qmi(i, p - 2, p) % p;
}
return res;
}
int lucas(ll a, ll b, int p)
{
if (a < p && b < p) return C(a, b, p);
return (ll)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main()
{
int n;
scanf("%d", &n);
while (n--)
{
ll a, b;
int p;
scanf("%lld%lld%d", &a, &b, &p);
printf("%d\n", lucas(a, b, p));
}
return 0;
}
4.将组合数分解质因数+高精度乘低精度
题目描述
输入
a
,
b
a, b
a,b,求
C
a
b
C_a^b
Cab 的值。注意结果可能很大,需要使用高精度计算。
数据范围
1
≤
b
≤
a
≤
5000
1 \le b \le a \le 5000
1≤b≤a≤5000
思路
当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用。
C
a
b
=
a
∗
(
a
−
1
)
∗
.
.
.
∗
(
a
−
b
+
1
)
b
∗
(
b
−
1
)
∗
.
.
.
∗
1
=
a
!
b
!
∗
(
a
−
b
)
!
C_a^b = \frac{a*(a-1)*...*(a-b+1)}{b*(b-1)*...*1} = \frac{a!}{b!*(a-b)!}
Cab=b∗(b−1)∗...∗1a∗(a−1)∗...∗(a−b+1)=b!∗(a−b)!a!
首先筛选出范围内的所有质数,然后将组合数分解质因数为
p
1
c
1
∗
p
2
c
2
∗
.
.
.
∗
p
k
c
k
p_1^{c_1} * p_2^{c_2} * ... * p_k^{c_k}
p1c1∗p2c2∗...∗pkck,最后用高精度乘法将所有质因子乘起来即可。
如何求
n
!
n!
n! 中有多少个质因子
p
p
p 呢?
⌊
n
p
⌋
+
⌊
n
p
2
⌋
+
⌊
n
p
3
⌋
+
.
.
.
\lfloor \frac{n}{p} \rfloor+\lfloor \frac{n}{p^2} \rfloor+\lfloor \frac{n}{p^3} \rfloor+...
⌊pn⌋+⌊p2n⌋+⌊p3n⌋+...
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 5010;
int primes[N], cnt;
bool st[N];
int sum[N];
// 线性筛
void get_primes(int n)
{
for (int i = 2; i <= n; i++)
{
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++)
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
// 求n!中有多少个质因子p
int get(int n, int p)
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
// 高精度乘以低精度
vector<int> mul(vector<int> a, int b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size(); i++)
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
int main()
{
int a, b;
scanf("%d%d", &a, &b);
get_primes(a);
for (int i = 0; i < cnt; i++)
{
int p = primes[i];
sum[i] = get(a, p) - get(b, p) - get(a - b, p);
}
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i++)
{
for (int j = 0; j < sum[i]; j++)
{
res = mul(res, primes[i]);
}
}
for (int i = res.size() - 1; i >= 0; i--)
{
printf("%d", res[i]);
}
return 0;
}