目录
- 几个定理
-
- 唯一分解定理
- 鸽巢原理(抽屉原理)
- 麦乐鸡定理
- 哥德巴赫猜想
- 容斥原理
-
- 例题
- 二进制枚举解
- dfs解
- 裴蜀定理
-
- 例题
- 代码
- 最大公约数、最小公倍数
-
- 最大公约数
- 最小公倍数
- 质数
-
- 试除法判断质数
- 分解质因数
- 筛质数
-
- 朴素筛法(埃氏筛法)
- 线性筛法(欧拉筛法)
- 约数
-
- 试除法求约数
- 求约数个数
-
- 一个数求约数个数
- 求1~n所有数的约数个数
-
- O ( n l o g n ) O(nlogn) O(nlogn)筛法
- O ( n ) O(n) O(n)筛法
- 约数之和
-
- 一个数求约数之和
- 求1~n所有数的"约数之和"
-
- O ( n l o g n ) O(nlogn) O(nlogn)筛法
- O ( n ) O(n) O(n)筛法
- 欧拉函数
-
- 定义求一个数的欧拉函数
- 筛法求1~n每个数的欧拉函数
- 快速幂
-
- 快速幂求a的b次方
-
- 不带取模
- 带取模
- 拓展欧几里得算法
-
- 原理
- 拓展欧几里得算法求解线性同余方程
- 逆元
-
- 快速幂求逆元
- 拓展欧几里得算法求逆元
- 预处理1~n的逆元
- 中国剩余定理
-
- 中国剩余定理求解线性同余方程组(所有a彼此互质)
- 中国剩余定理求解线性同余方程组(所有a不一定互质)
- 组合数
-
- 一组测试数据
- 几个性质
-
- 组合数的两种表示
- 递推式
- 阶乘式
- 卢卡斯定理
- 递推求组合数
- 预处理阶乘求组合数
- 卢卡斯定理求组合数
- 卢卡斯定理 + 预处理阶乘求组合数
本文中默认:
using LL = long long;
几个定理
唯一分解定理
对于一个大于1的正整数 n n n, n n n可以分解质因数为:
n = Π i = 1 k p i a i = p 1 a 1 × p 2 a 2 × . . . × p k a k n=\Pi_{i=1}^k p_i^{a_i}=p_1^{a_1} \times p_2^{a_2} \times ... \times p_k^{a_k} n=Πi=1kpiai=p1a1×p2a2×...×pkak
n n n的质因子 p p p的个数:
∑ i = 1 n N p i ∑^n_{i=1} \frac{N}{p^i} i=1∑npiN
约数定理 :
d ( n ) = ∏ i = 1 k ( a i + 1 ) = ( a 1 + 1 ) ( a 2 + 1 ) . . . ( a k + 1 ) d(n)=∏^k_{i=1}(a_i+1)=(a_1+1)(a_2+1)...(a_k+1) d(n)=i=1∏k(ai+1)=(a1+1)(a2+1)...(ak+1)
鸽巢原理(抽屉原理)
桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,我们会发现至少会有一个抽屉里面放不少于两个苹果。这一现象就是我们所说的"抽屉原理"。 抽屉原理的一般含义为:"如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素。" 抽屉原理有时也被称为鸽巢原理。
麦乐鸡定理
给定两个互质的数 n , m n,m n,m ,定义 x = a ∗ n + b ∗ m ( a ≥ 0 , b ≥ 0 ) x=a*n+b*m(a \ge 0,b \ge 0) x=a∗n+b∗m(a≥0,b≥0),当 x > n ∗ m − n − m x > n*m-n-m x>n∗m−n−m 时,该式子恒成立。
哥德巴赫猜想
任何一个大于 5 5 5 的整数都可写成三个质数之和;任何一个大于 2 2 2 的偶数都可写成两个素数之和。
容斥原理
对于 n n n个集合 A 1 , A 2 , A 3 , . . . A n : A_1,A_2, A_3,...A_n: A1,A2,A3,...An: ( A 1 ∪ A 2 ∪ A 3 ∪ . . . ∪ A n ) = ∑ 0 < i ≤ n A i − ∑ 0 < i < j ≤ n ( A i ∩ A j ) + ∑ 0 < i < j < k ≤ n ( A i ∩ A j ∩ A j ) + . . . + ( − 1 ) n + 1 ( A 1 ∩ A 2 ∩ A 3 ∩ . . . ∩ A n ) (A_{1} \cup A_{2} \cup A_{3} \cup ... \cup A_{n})=\sum_{0<i\le n} A_{i}-\sum_{0<i<j\le n} (A_{i} \cap A_{j})+\sum_{0<i<j<k\le n}(A_{i}\cap A_{j}\cap A_{j})+...+(-1)^{n+1}(A_{1} \cap A_{2}\cap A_{3}\cap ... \cap A_{n}) (A1∪A2∪A3∪...∪An)=0<i≤n∑Ai−0<i<j≤n∑(Ai∩Aj)+0<i<j<k≤n∑(Ai∩Aj∩Aj)+...+(−1)n+1(A1∩A2∩A3∩...∩An)(加减加减加减...)
例题
给定一个整数 n n n 和 m m m 个不同的质数 p 1 , p 2 , . . . , p m p_1, p_2, ..., p_m p1,p2,...,pm,请你求出 1 ∼ n n n 中能被 p 1 , p 2 , . . . , p m p_1, p_2, ..., p_m p1,p2,...,pm 中的至少一个数整除的整数有多少个。
二进制枚举解
cpp
LL n, m;
LL p[N];
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin >> n >> m;
for (int i = 0; i < m; i ++ )
cin >> p[i];
LL ans = 0;
for (int i = 1; i < (1 << m); i ++ ){
LL t = 1, cnt = 0;
for (int j = 0; j < m; j ++ ){
if (i >> j & 1){
cnt ++ ;
t *= p[j];
if (t > n){
t = -1;
break;
}
}
}
if (t != -1){
if (cnt & 1) ans += n / t;
else ans -= n / t;
}
}
cout << ans << endl;
return 0;
}
dfs解
cpp
LL n, res;
int m;
LL p[N];
void dfs(int x, LL s, LL odd) {
if (x == m) {
if (s != 1) ans += odd * (n / s);
return;
}
dfs(x + 1, s, odd);
if (s <= n / p[x]) dfs(x + 1, s * p[x], -odd);
}
int main() {
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> p[i];
}
res = 0;
dfs(0, 1, -1);
cout << res << endl;
return 0;
}
裴蜀定理
如果 a a a 和 b b b 是两个整数,则存在整数 x , y x ,y x,y 使得:
a ⋅ x + b ⋅ y = gcd ( a , b ) a \cdot x + b \cdot y = \gcd(a, b) a⋅x+b⋅y=gcd(a,b)
例题
给定一个序列 a a a,找到一个序列 x x x,使得 ∑ i = 1 n a i x i \sum_{i = 1}^n a_ix_i ∑i=1naixi 最小。
代码
cpp
LL n, a, ans;
LL gcd(LL a, LL b){
return b ? gcd(b, a % b) : a;
}
int main(){
cin >> n;
for (int i = 0; i < n; i ++ ){
cin >> a;
if (a < 0) a = -a;
ans = gcd(ans, a);
}
cout << ans << endl;
return 0;
}
最大公约数、最小公倍数
最大公约数
欧几里得算法(辗转相除法)
cpp
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
或者直接
cpp
__gcd(a, b);
最小公倍数
a 和 b 的 最 小 公 倍 数 = a × b a 和 b 的 最 大 公 约 数 a和b的最小公倍数=\frac{a \times b}{a和b的最大公约数} a和b的最小公倍数=a和b的最大公约数a×b
防止越界,先除再乘。
cpp
int lcm(int a, int b){
return a / gcd(a,b) * b ;
}
质数
试除法判断质数
见"C语言入门讲义"
cpp
bool is_prime(int x){
if(x < 2) return false;
for(int i = 2; i <= x / i; i++){
if(x % i == 0) return false;
}
return true;
}
分解质因数
分解出 n n n的 k k k个质因数: n = i 1 s 1 × i 2 s 2 × . . . × i k s k n = i_1^{s_1} \times i_2^{s_2} \times ... \times i_k^{s_k} n=i1s1×i2s2×...×iksk
cpp
void divide(int x){
for(int i = 2; i <= x / i; i++){
if(x % i == 0){
int s = 0;
while(x % i == 0){
x /= i;
s++;
}
cout << i << ' ' << s << endl; // i:底数 s:指数
}
}
if(x != 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
筛质数
将 1 1 1~ n n n中所有的质数筛到 p r i m e s primes primes数组中
朴素筛法(埃氏筛法)
cpp
vector<int> primes;
bool not_prime[N]; // not_prime[i] = true 表示 i 不是质数
void get_primes(int n){
for(int i = 2; i <= n; i++){
if(not_prime[i]) continue;
primes.push_back(i);
for(int j = i + i; j <= n; j += i) not_prime[j] = true;
}
}
线性筛法(欧拉筛法)
cpp
vector<int> primes;
bool not_prime[N]; // not_prime[i] = true 表示 i 不是质数
void get_primes(int n){
for(int i = 2; i <= n; i++){
if(!not_prime[i]) primes.push_back(i);
for(int p : primes){
if(i * p > n) break;
not_prime[i * p] = true;
if(i % p == 0) break;
}
}
}
约数
试除法求约数
cpp
vector<int> get_divisors(int x){
vector<int> res;
for(int i = 1; i <= x / i; i ++)
if(x % i == 0){
res.push_back(i);
if(i != x / i) res.push_back(x / i);
}
sort(res.begin(), res.end());
return res; // 返回一个vector,里面是n的所有约数(因子)
}
求约数个数
一个数求约数个数
cpp
int get_divisors(int x){
unordered_map<int, int> primes; // 存x的质因数和其对应的次数
for(int i = 2; i <= x / i;i++){
while(x % i == 0){
x /= i;
primes[i]++;
}
}
if(x > 1) primes[x]++; // 如果最后剩下一个大于 1 的数,说明它也是一个质因数
LL res = 1;
for(auto p : primes) res = res * (p.second + 1); // 约数定理
return res;
}
求1~n所有数的约数个数
O ( n l o g n ) O(nlogn) O(nlogn)筛法
cpp
int cnt[N]; // cnt[i] : i 的约数个数
for(int i = 1; i <= n; i++){
for(int j = i; j <= n; j += i){
cnt[j]++;
}
}
O ( n ) O(n) O(n)筛法
cpp
bool not_prime[N];
int d[N], num[N]; // d[i] 表示 i 的约数个数,num[i] 表示 i 的最后一个质因子的幂(从 i 分解质因数得到的最后一个质因子 j,i = a * j^num)
vector<int> primes; // 筛质数
void get_divisors(int n){
d[1] = 1;
num[1] = 0;
for(int i = 2; i <= n; i++){
if(!not_prime[i]){
primes.push_back(i);
num[i] = 1;
d[i] = 2;
}
for(int p : primes){
if(i * p > n) break;
not_prime[i * p] = true;
if(i % p == 0){
num[i * p] = num[i] + 1;
d[i * p] = d[i] / (num[i] + 1) * (num[i * p] + 1);
break;
}
else{
num[i * p] = 1;
d[i * p] = d[i] * 2;
}
}
}
}
约数之和
一个数求约数之和
cpp
LL get_divisor_sum(int x){
unordered_map<int, int> primes; // 存x的质因数和其对应的次数
for(int i = 2; i <= x / i; i++){
while(x % i == 0){
x /= i;
primes[i]++;
}
}
if(x > 1) primes[x]++;
LL res = 1;
for(auto p : primes){
LL t = 1, sum = 1;
for(int i = 0; i < p.second; i++){
t *= p.first;
sum += t;
}
res *= sum;
}
return res; // res表示x的所有约数的和
}
求1~n所有数的"约数之和"
O ( n l o g n ) O(nlogn) O(nlogn)筛法
cpp
LL sum[N]; // sum[i]表示i的约数和
for(int i = 1; i <= n; i++){
for(int j = i; j <= n; j += i){
sum[j] += i;
}
}
O ( n ) O(n) O(n)筛法
cpp
bool not_prime[N];
int num[N]; // num[i] 表示 i 的最后一个质因子的幂次(i = a * p^num)
vector<int> primes;
LL sd[N], sp[N]; // sd[i] 表示 i 的所有约数之和, sp[i] 表示 i 最后一个质因子的贡献项(1 + p + p^2 + ... + p^k)
void get_divisors(int n){
sd[1] = 1;
num[1] = 0;
sp[1] = 1;
for(int i = 2; i <= n; i++){
if(!not_prime[i]){
primes.push_back(i);
num[i] = 1;
sp[i] = 1 + i;
sd[i] = 1 + i;
}
for(int p : primes){
if(i * p > n) break;
not_prime[i * p] = true;
if(i % p == 0){
num[i * p] = num[i] + 1;
sp[i * p] = sp[i] * p + 1;
sd[i * p] = sd[i] / sp[i] * sp[i * p];
break;
}
else{
num[i * p] = 1;
sp[i * p] = 1 + p;
sd[i * p] = sd[i] * sp[i * p];
}
}
}
}
欧拉函数
1 ∼ N 1 \sim N 1∼N 中与 N N N 互质的数的个数被称为欧拉函数,记为 φ ( N ) \varphi(N) φ(N)。
若在算术基本定理中, N = p 1 a 1 p 2 a 2 ... p m a m N = p_1^{a_1} p_2^{a_2} \dots p_m^{a_m} N=p1a1p2a2...pmam,则:
φ ( N ) = N × p 1 − 1 p 1 × p 2 − 1 p 2 × ⋯ × p m − 1 p m \varphi(N) = N \times \frac{p_1 - 1}{p_1} \times \frac{p_2 - 1}{p_2} \times \dots \times \frac{p_m - 1}{p_m} φ(N)=N×p1p1−1×p2p2−1×⋯×pmpm−1
定义求一个数的欧拉函数
cpp
int phi(int x){
int res = x;
for(int i = 2; i <= x / i; i ++){
if(x % i == 0){
res = res / i * (i - 1);
while(x % i == 0) x /= i;
}
}
if(x > 1) res = res / x * (x - 1);
return res; // res即为phi(x)的值
}
筛法求1~n每个数的欧拉函数
cpp
vector<int> primes;
bool not_prime[N];
int euler[N]; // eular[i]表示phi(i)的值
void get_eulers(int n){
euler[1] = 1;
for(int i = 2; i <= n; i++){
if(!not_prime[i]){
primes.push_back(i);
euler[i] = i - 1;
}
for(int p : primes){
LL t = 1LL * i * p;
if(t > n) break;
not_prime[t] = true;
if(i % p == 0){
euler[t] = euler[i] * p;
break;
}
else{
euler[t] = euler[i] * (p - 1);
}
}
}
}
快速幂
快速幂求a的b次方
不带取模
cpp
LL qmi(int a, int b){
LL res = 1;
while(b){
if(b & 1) res = res * a;
a = (LL)a * a;
b >>= 1;
}
return res; // pow(a, b)
}
带取模
cpp
LL qmi(int a, int b, int p){
LL res = 1;
while(b){
if(b & 1) res = res * a % p;
a = (LL)a * a % p;
b >>= 1;
}
return res; // pow(a, b) % p
}
拓展欧几里得算法
原理
给定 n n n 对正整数 a i , b i a_i, b_i ai,bi,对于每对数,求出一组 x i , y i x_i, y_i xi,yi,使其满足 a i × x i + b i × y i = gcd ( a i , b i ) a_i \times x_i + b_i \times y_i = \gcd(a_i, b_i) ai×xi+bi×yi=gcd(ai,bi)。
cpp
int exgcd(int a, int b, int &x, int &y){ // 一定要加 &
if(!b){
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main(){
int n;
cin >> n;
while(n --){
int a, b;
cin >> a >> b;
int x, y;
exgcd(a, b, x, y);
cout << x << ' ' << y << endl;
}
return 0;
}
拓展欧几里得算法求解线性同余方程
给定 n n n 组数据 a i , b i , m i a_i, b_i, m_i ai,bi,mi,对于每组数求出一个 x i x_i xi,使其满足 a i × x i ≡ b i ( m o d m i ) a_i \times x_i \equiv b_i \pmod{m_i} ai×xi≡bi(modmi),如果无解则输出 impossible
。
cpp
int exgcd(int a, int b, int &x, int &y){ // 一定要加 &
if(!b){
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main(){
int n;
cin >> n;
while(n--){
int a, b, m;
cin >> a >> b >> m;
int x, y;
int d = exgcd(a, m, x, y);
if(b % d) cout << "impossible" << endl;
else cout << (LL)b / d * x % m << endl;
}
return 0;
}
逆元
b b b的乘法逆元,记作: b − 1 b^{-1} b−1。
a b ( m o d p ) = a × b − 1 ( m o d p ) \frac{a}{b}\pmod p = a \times b^{-1} \pmod p ba(modp)=a×b−1(modp)
(将题中的运算除法变成运算乘法)
快速幂求逆元
适用条件:模数 p p p一定要是质数,并且 n n n和 p p p互质,即 g c d ( n , p ) = 1 gcd(n, p) = 1 gcd(n,p)=1
cpp
LL qmi(int a, int b, int p){
LL res = 1;
while(b){
if(b & 1) res = res * a % p;
a = (LL)a * a % p;
b >>= 1;
}
return res;
}
LL inv(int n, int p){ // n 在模 p下的逆元
return qmi(n, p - 2, p);
}
拓展欧几里得算法求逆元
适用条件:模数 p p p可以是任何数,只要满足 n n n和 p p p互质,即 g c d ( n , p ) = 1 gcd(n, p) = 1 gcd(n,p)=1即可。
cpp
LL exgcd(LL a, LL b, LL &x, LL &y){
if(!b){
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
LL inv(LL n, int p){ // n 在模 p下的逆元
LL x, y;
LL d = exgcd(n, p, x, y);
if(d == 1) return (x % p + p) % p;
else return -1;
}
预处理1~n的逆元
cpp
LL inv[N]; // inv[i] : i 在模 p 下的逆元
inv[1] = 1;
for(int i = 2; i <= n; i++){
inv[i] = (p - (p / i) % p) * inv[p % i] % p;
}
中国剩余定理
中国剩余定理求解线性同余方程组(所有a彼此互质)
给定 2 n 2n 2n 个整数 a 1 , a 2 , ... , a n a_1, a_2, \ldots, a_n a1,a2,...,an 和 m 1 , m 2 , ... , m n m_1, m_2, \ldots, m_n m1,m2,...,mn,求一个最小的非负整数 x x x,满足
∀ i ∈ [ 1 , n ] , x ≡ m i ( m o d a i ) \forall i \in [1, n],\ x \equiv m_i \pmod{a_i} ∀i∈[1,n], x≡mi(modai)。
cpp
LL exgcd(LL a, LL b, LL &x, LL &y){
if(!b){
x = 1;
y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
LL inv(LL n, LL p){
LL x, y;
LL d = exgcd(n, p, x, y);
if(d != 1) return -1;
return (x % p + p) % p;
}
LL CRT(LL a[], LL m[], int n){
LL mm = 1;
for(int i = 0; i < n; i++){
mm *= m[i];
}
LL res = 0;
for(int i = 0; i < n; i++){
LL t = mm / m[i];
LL inv_t = inv(t, m[i]);
res = (res + t * inv_t % mm * (a[i] % mm)) % mm;
}
if(res < 0) res += mm;
return res;
}
中国剩余定理求解线性同余方程组(所有a不一定互质)
cpp
LL exgcd(LL a, LL b, LL &x, LL &y){
if(!b){
x = 1;
y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
LL exCRT(LL a[], LL m[], int n){
LL M = m[0], A = a[0];
for(int i = 1; i < n; i++){
LL x, y;
LL d = exgcd(M, m[i], x, y);
if((a[i] - A) % d != 0) return -1;
LL t = m[i] / d;
x = x * ((a[i] - A) / d);
x = (x % t + t) % t;
A += x * M;
M = M / d * m[i];
A %= M;
}
A = (A % M + M) % M;
return A;
}
组合数
一组测试数据
提供一组测试数据: C 132 66 = C_{132}^{66}= C13266= 377'389'666'165'540'953'244'592'352'291'892'721'700,模数为 998244353 998244353 998244353 时为 24 1 ′ 20 0 ′ 029 241'200'029 241′200′029; 1 0 9 + 7 10^9+7 109+7 时为 598375978 598375978 598375978。
几个性质
组合数的两种表示
( n k ) ⇔ C n k \dbinom{n}{k} \Leftrightarrow C_n^k (kn)⇔Cnk
递推式
C n m = C n m − 1 + C n − 1 m − 1 C_n^m = C_n^{m - 1} + C_{n - 1}^{m - 1} Cnm=Cnm−1+Cn−1m−1
阶乘式
C n m = n ! m ! × ( n − m ) ! C_n^m = \frac{n!}{m! \times (n - m)!} Cnm=m!×(n−m)!n!
卢卡斯定理
对于素数 p p p,有
C n k ≡ C ⌊ n / p ⌋ ⌊ k / p ⌋ × C n m o d p k m o d p ( m o d p ) . \mathrm{C}n^k \equiv \mathrm{C}{\lfloor n/p \rfloor}^{\lfloor k/p \rfloor} \times \mathrm{C}_{n \bmod p}^{k \bmod p} \pmod{p}. Cnk≡C⌊n/p⌋⌊k/p⌋×Cnmodpkmodp(modp).
其中,当 n < k n < k n<k 时,组合数 C n k \mathrm{C}_n^k Cnk 规定为 0 0 0。
递推求组合数
适用条件: C a b C_a^b Cab中的 a a a和 b b b都比较小,二维数组 C [ a ] [ b ] C[a][b] C[a][b]要能存的下
例如: 1 ≤ n ≤ 1 0 5 , 1 ≤ b ≤ a ≤ 2000 1\le n \le 10^5,1 \le b \le a \le 2000 1≤n≤105,1≤b≤a≤2000
cpp
const int N = 2020;
const int mod = 1e9 + 7;
int C[N][N]; // C[a][b] 即为C_a^b
void init() {
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;
}
}
}
预处理阶乘求组合数
用快速幂预处理阶乘和逆元求组合数。
适用条件: a a a和 b b b的范围适中,二维数组存不下但可以开一维。
比如: 1 ≤ n ≤ 1 0 5 , 1 ≤ b ≤ a ≤ 1 0 5 1 \le n \le 10^5, 1 \le b \le a \le 10^5 1≤n≤105,1≤b≤a≤105
cpp
LL qmi(int a, int b, int p){
LL res = 1;
while(b){
if(b & 1) res = res * a % p;
a = (LL)a * a % p;
b >>= 1;
}
return res;
}
LL inv(LL n, int p){
return qmi(n, p - 2, p);
}
LL fact[N], infact[N]; // 预处理阶乘和阶乘的逆元
LL Cnk(int n, int k){ // Cnk即为C_n^k
if(k < 0 || k > n) return 0;
return (LL)fact[n] * infact[k] % mod * infact[n - k] % mod;
}
void init(){
fact[0] = 1;
for(int i = 1; i < N; i++){
fact[i] = fact[i - 1] * i % mod;
}
infact[N - 1] = inv(fact[N - 1]);
for(int i = N - 1; i > 0; i--){
infact[i - 1] = infact[i] * i % mod;
}
}
卢卡斯定理求组合数
适用情况: a a a和 b b b特别大,用数组存不下,无法预处理,且查询较少,每次直接用定理求组合数不会超时。(模数必须为质数)
比如: 1 ≤ n ≤ 20 , 1 ≤ b ≤ a ≤ 1 0 18 1 \le n \le 20,1 \le b \le a \le 10^{18} 1≤n≤20,1≤b≤a≤1018
cpp
LL qmi(LL a, LL b, int p){
LL res = 1;
while(b){
if(b & 1) res = res * a % p;
a = (LL)a * a % p;
b >>= 1;
}
return res;
}
int Cnk(int a, int b, int p){ // 较小数(0~p-1)的组合数
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;
}
// c[a][b] = c[a % p][b % p] * c[a / p][b / p]
int lucas(LL a, LL b, int p){ // lucas(a, b, p) 即为C_a^b % p
if(a < p && b < p) return C(a, b, p);
return (LL)Cnk(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
卢卡斯定理 + 预处理阶乘求组合数
适用情况: a a a和 b b b特别大,用数组存不下,无法预处理,且查询多,每次都求一遍组合数会超时。(模数必须为质数)
cpp
LL qmi(LL a, LL b, int p){
LL res = 1;
while(b){
if(b & 1) res = res * a % p;
a = (LL)a * a % p;
b >>= 1;
}
return res;
}
LL fact[N], infact[N];
void init(){
fact[0] = 1;
for(int i = 1; i < p; i++){
fact[i] = fact[i - 1] * i % mod;
}
infact[p - 1] = inv(fact[p - 1]);
for(int i = p - 1; i > 0; i--){
infact[i - 1] = infact[i] * i % mod;
}
}
LL Cnk(int n, int k){ // 较小数(0~p-1)的组合数
if(k < 0 || k > n) return 0;
return (LL)fact[n] * infact[k] % mod * infact[n - k] % mod;
}
// c[a][b] = c[a % p][b % p] * c[a / p][b / p]
int lucas(LL a, LL b, int p){ // lucas(a, b, p) 即为C_a^b % p
if(a < p && b < p) return C(a, b, p);
return (LL)Cnk(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}