数学
向上取整
int ceil = (a + b - 1) / b
int ceil = (a + b - 1) / b
加法原理
完成一个工程可以有
乘法原理
完成一个工程需要分
算术基本定理
任何一个大于
其中
裴蜀定理
裴蜀定理,又称贝祖定理(Bézout's lemma)。是一个关于最大公约数的定理。
其内容是:
设
平方和公式
质数
试除法判质数
模板代码:
boolean isPrime(int n) {
if (n < 2) return false;
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) return false;
}
return true;
}
boolean isPrime(int n) {
if (n < 2) return false;
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) return false;
}
return true;
}
bool is_prime(int n) {
if (n < 2) return false;
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) return false;
}
return true;
}
bool is_prime(int n) {
if (n < 2) return false;
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) return false;
}
return true;
}
时间复杂度:
试除法分解质因数
分解质因数(Prime Factorization):根据算术基本定理,每个合数都可以写成几个质数相乘的形式,其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数。
模板题:【数学】分解质因数。
原理:试除法,枚举
模板代码:
void pf(int n) {
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) {
int s = 0;
while (n % x == 0) {
n /= x;
++s;
}
System.out.println(x + " " + s);
}
}
if (n > 1) System.out.println(n + " 1");
}
void pf(int n) {
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) {
int s = 0;
while (n % x == 0) {
n /= x;
++s;
}
System.out.println(x + " " + s);
}
}
if (n > 1) System.out.println(n + " 1");
}
void pf(int n) {
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) {
int s = 0;
while (n % x == 0) {
n /= x;
++s;
}
printf("%d %d\n", x, s);
}
}
if (n > 1) printf("%d 1\n", n);
}
void pf(int n) {
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) {
int s = 0;
while (n % x == 0) {
n /= x;
++s;
}
printf("%d %d\n", x, s);
}
}
if (n > 1) printf("%d 1\n", n);
}
时间复杂度:
- 最差:
; - 最好:
。
其中枚举的 if (n % i == 0)
成立的时候,
筛质数
模板题:【数学】筛质数。
朴素筛
原理:
- 枚举
, 的倍数一定是合数,所以可以在枚举到 的时候筛掉所有 小于等于 的 的倍数 。 - 而在枚举到
的时候,如果 还没有被筛掉,说明 不是 中任意一个数的倍数,所以 一定是质数。
模板代码:
int countPrimes(int n) {
int cnt = 0;
int[] primes = new int[n + 1];
boolean[] isNotPrime = new boolean[n + 1];
for (int x = 2; x <= n; ++x) {
if (!isNotPrime[x]) primes[cnt++] = x;
for (int k = 2; k * x <= n; ++k) isNotPrime[k * x] = true;
}
return cnt;
}
int countPrimes(int n) {
int cnt = 0;
int[] primes = new int[n + 1];
boolean[] isNotPrime = new boolean[n + 1];
for (int x = 2; x <= n; ++x) {
if (!isNotPrime[x]) primes[cnt++] = x;
for (int k = 2; k * x <= n; ++k) isNotPrime[k * x] = true;
}
return cnt;
}
时间复杂度:
埃氏筛
原理:要得到自然数
证明:根据算术基本定理:每一个大于
模板代码:
int countPrimes(int n) {
int cnt = 0;
int[] primes = new int[n + 1];
boolean[] isNotPrime = new boolean[n + 1];
for (int x = 2; x <= n; ++x) {
if (!isNotPrime[x]) {
primes[cnt++] = x;
for (int k = 2; k * x <= n; ++k) isNotPrime[k * x] = true;
}
}
return cnt;
}
int countPrimes(int n) {
int cnt = 0;
int[] primes = new int[n + 1];
boolean[] isNotPrime = new boolean[n + 1];
for (int x = 2; x <= n; ++x) {
if (!isNotPrime[x]) {
primes[cnt++] = x;
for (int k = 2; k * x <= n; ++k) isNotPrime[k * x] = true;
}
}
return cnt;
}
时间复杂度:
线性筛(欧拉筛)
原理:线性筛保证了每个合数都只被其最小质因子筛掉。
模板代码:
int countPrimes(int n) {
int cnt = 0;
int[] primes = new int[n + 1];
boolean[] isNotPrime = new boolean[n + 1];
for (int x = 2; x <= n; ++x) {
if (!isNotPrime[i]) primes[cnt++] = x;
for (int i = 0; primes[i] <= n / x; ++i) {
isNotPrime[primes[i] * x] = true;
if (x % primes[i] == 0) break;
}
}
return cnt;
}
int countPrimes(int n) {
int cnt = 0;
int[] primes = new int[n + 1];
boolean[] isNotPrime = new boolean[n + 1];
for (int x = 2; x <= n; ++x) {
if (!isNotPrime[i]) primes[cnt++] = x;
for (int i = 0; primes[i] <= n / x; ++i) {
isNotPrime[primes[i] * x] = true;
if (x % primes[i] == 0) break;
}
}
return cnt;
}
int cnt, primes[N];
bool np[N];
int sieve(int n) {
for (int x = 2; x <= n; ++x) {
if (!np[x]) primes[cnt++] = x;
for (int i = 0; primes[i] <= n / x; ++i) {
np[primes[i] * x] = true;
if (x % primes[i] == 0) break;
}
}
return cnt;
}
int cnt, primes[N];
bool np[N];
int sieve(int n) {
for (int x = 2; x <= n; ++x) {
if (!np[x]) primes[cnt++] = x;
for (int i = 0; primes[i] <= n / x; ++i) {
np[primes[i] * x] = true;
if (x % primes[i] == 0) break;
}
}
return cnt;
}
时间复杂度:
线性筛是如何保证每个合数只会被其最小质因子筛掉的?
- 当
x % primes[i] == 0
时,说明此时遍历到的不是 的质因子,那么 ** 一定小于 的最小质因子 **,所以 的最小质因子就是 ; - 当
x % primes[i] != 0
时 ,说明的最小质因子是此时的 ,因此 的最小质因子依旧应该是 ,但如果继续枚举的话,我们就把 这个数筛掉了,虽然这个数也是合数,但是筛掉它的时候并不是用它的最小质因数筛掉的 ,而是利用 和 把它删掉的,而这个数的最小质因数其实是 ,如果不在这里退出循环的话,就会发现有些数是被重复筛了的。
勒让德定理
在正数
模板代码:
int count(int n, int p) {
int res = 0;
while (n) res += n /= p;
return res;
}
int count(int n, int p) {
int res = 0;
while (n) res += n /= p;
return res;
}
约数
试除法求所有约数
模板代码:
set<int> get_divisors(int n) {
set<int> divisors;
for (int i = 1; i <= n / i; ++i) {
if (n % i == 0) {
divisors.insert(i);
divisors.insert(n / i);
}
}
return divisors;
}
set<int> get_divisors(int n) {
set<int> divisors;
for (int i = 1; i <= n / i; ++i) {
if (n % i == 0) {
divisors.insert(i);
divisors.insert(n / i);
}
}
return divisors;
}
时间复杂度:
int
范围内,约数最多的数其约数有个。
约数个数
求约数个数
模板题:【数学】约数个数。
原理:
- 根据算术基本定理把
质因数分解为: ; - 那么
的约数个数为: 。
证明:
- 显然,
的约数为 ,同理: 的约数为 ; - 实际上
的约数是在 每个数的约数中分别挑一个相乘得来的,那么 的每个约数就都可以被分解为 (其中 ); - 又根据算术基本定理:每一个数的质因数分解是唯一的,那么
的所有组合就对应了 的所有约数; - 而每个
都有 共 种选择,所以 共有 个组合,那么 也就有这么多个约数。
模板代码:
// 求一个数的约数个数
int count_divisors(int n) {
int cnt = 1;
for (int i = 2; i <= n / i; ++i) {
if (n % i == 0) {
int s = 0;
while (n % i == 0) {
n /= i;
++s;
}
cnt *= s + 1;
}
}
if (n > 1) cnt *= 2;
return cnt;
}
// 求一个数的约数个数
int count_divisors(int n) {
int cnt = 1;
for (int i = 2; i <= n / i; ++i) {
if (n % i == 0) {
int s = 0;
while (n % i == 0) {
n /= i;
++s;
}
cnt *= s + 1;
}
}
if (n > 1) cnt *= 2;
return cnt;
}
时间复杂度:
- 最差:
; - 最好:
。
一些与约数个数相关的性质
中所有数的约数个数之和总和约为 个; 范围内的约数个数最多的数的约数约有 1600 个。
约数之和
模板题:【数学】约数之和。
原理:
根据算术基本定理把
质因数分解为: ; 那么
的约数之和为:
证明:
显然,
的约数为 ,同理: 的约数为 ; 实际上
的约数是在 每个数的约数中分别挑一个相乘得来的; 可知共有
种挑法,即约数个数。 由乘法原理可知它们的和为:
模板代码:
// 求一个数的约数之和
int sum_divisors(int n) {
int sum = 1;
for (int i = 2; i <= n / i; ++i) {
if (n % i == 0) {
int tmp = 1;
while (n % i == 0) {
n /= i;
tmp = i * tmp + 1;
}
sum *= tmp;
}
}
if (n > 1) sum *= n + 1;
return sum;
}
// 求一个数的约数之和
int sum_divisors(int n) {
int sum = 1;
for (int i = 2; i <= n / i; ++i) {
if (n % i == 0) {
int tmp = 1;
while (n % i == 0) {
n /= i;
tmp = i * tmp + 1;
}
sum *= tmp;
}
}
if (n > 1) sum *= n + 1;
return sum;
}
最大公约数
原理:辗转相除法(欧几里得算法):
证明:
- 已知
,设 ,则现在要证明 ; - 对于
, 设 : - 则显然有
,当 时, ; 同时成立,则有 。
- 则显然有
- 对于
,设 : - 则显然有
; 同时成立,则有 。
- 则显然有
得证,同时 也成立。
模板代码:(递归实现)
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
欧拉函数
公式求欧拉函数
详细介绍及原理:欧拉函数 - OI Wiki。
若在算术基本定理中:
那么
模板代码:
typedef long long LL;
// 求一个数的欧拉函数
LL phi(int n) {
LL res = n;
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) {
while (n % x == 0) n /= x;
res = res * (x - 1) / x;
}
}
if (n > 1) res = res * (n - 1) / n;
return res;
}
typedef long long LL;
// 求一个数的欧拉函数
LL phi(int n) {
LL res = n;
for (int x = 2; x <= n / x; ++x) {
if (n % x == 0) {
while (n % x == 0) n /= x;
res = res * (x - 1) / x;
}
}
if (n > 1) res = res * (n - 1) / n;
return res;
}
时间复杂度:
筛法求欧拉函数之和
求
long sumEulers(int n) {
int cnt = 0;
int[] primes = new int[n + 1], phi = new int[n + 1];
boolean[] isNotPrime = new boolean[n + 1];
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!isNotPrime[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; ++j) {
isNotPrime[primes[j] * i] = true;
if (i % primes[j] == 0) {
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
long res = 0L;
for (int i = 1; i <= n; ++i) res += phi[i];
return res;
}
long sumEulers(int n) {
int cnt = 0;
int[] primes = new int[n + 1], phi = new int[n + 1];
boolean[] isNotPrime = new boolean[n + 1];
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!isNotPrime[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; ++j) {
isNotPrime[primes[j] * i] = true;
if (i % primes[j] == 0) {
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
long res = 0L;
for (int i = 1; i <= n; ++i) res += phi[i];
return res;
}
时间复杂度:
快速幂
详细介绍及原理:快速幂 - OI Wiki。
模板代码:
long kasumi(long base, long exp, long mod) {
long res = 1L;
while (exp != 0) {
if ((exp & 1) != 0) res = res * base % mod;
base = base * base % mod;
exp >>= 1;
}
return res;
}
long kasumi(long base, long exp, long mod) {
long res = 1L;
while (exp != 0) {
if ((exp & 1) != 0) res = res * base % mod;
base = base * base % mod;
exp >>= 1;
}
return res;
}
typedef long long LL;
LL kasumi(LL base, LL exp, LL mod) {
LL res = 1LL;
while (exp) {
if (exp & 1) res = res * base % mod;
base = base * base % mod;
exp >>= 1;
}
return res;
}
typedef long long LL;
LL kasumi(LL base, LL exp, LL mod) {
LL res = 1LL;
while (exp) {
if (exp & 1) res = res * base % mod;
base = base * base % mod;
exp >>= 1;
}
return res;
}
慢速乘
防止乘数及模数很大但还在 long long
范围内时直接做乘爆 long long
的问题。
long slowMul(long a, long b, long m) {
long c = 0L;
while (b > 0) {
if ((b & 1) != 0) c = (c + a) % m;
a = (a + a) % m;
b >>= 1;
}
return c;
}
long slowMul(long a, long b, long m) {
long c = 0L;
while (b > 0) {
if ((b & 1) != 0) c = (c + a) % m;
a = (a + a) % m;
b >>= 1;
}
return c;
}
typedef long long LL;
LL slow_mul(LL a, LL b, LL m) {
LL c = 0LL;
while (b) {
if (b & 1) c = (c + a) % m;
a = (a + a) % m;
b >>= 1;
}
return c;
}
typedef long long LL;
LL slow_mul(LL a, LL b, LL m) {
LL c = 0LL;
while (b) {
if (b & 1) c = (c + a) % m;
a = (a + a) % m;
b >>= 1;
}
return c;
}
扩展欧几里得算法
扩展欧几里得算法(Extended Euclidean algorithm, EXGCD),常用于求
过程
设:
由欧几里得算法可知:
所以:
因为
将
模板代码
int x, y;
int exgcd(int a, int b) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a % b);
int t = x;
x = y;
y = t - a / b * y;
return d;
}
int x, y;
int exgcd(int a, int b) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a % b);
int t = x;
x = y;
y = t - a / b * y;
return d;
}
线性同余方程
定义
形如:
的方程称为 线性同余方程(Congruence Equation)。其中,
用扩展欧几里得算法求解
根据以下两个定理,可以求出线性同余方程
定理 1
很容易发现,经过转换后的方程很像裴蜀定理的结论(
于是找到方程的一个解:
定理 2
若
并且对任意整数
根据定理 2,可以从已求出的一个解,求出方程的所有解。实际问题中,往往要求出一个最小整数解,也就是一个特解(例如使用扩展欧几里得算法求解乘法逆元):
因为
模板题:
模板代码
int x, y;
int exgcd(int a, int b) {
if (b == 0) {
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b);
int t = x;
x = y;
y = t - a / b * y;
return d;
}
// ax === b (mod n) 的解
int solve(int a, int b, int n) {
int d = exgcd(a, n);
if (b % d != 0) return -1; // 不存在整数解
else return (int) (1L * x * (b / d) % n);
}
int x, y;
int exgcd(int a, int b) {
if (b == 0) {
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b);
int t = x;
x = y;
y = t - a / b * y;
return d;
}
// ax === b (mod n) 的解
int solve(int a, int b, int n) {
int d = exgcd(a, n);
if (b % d != 0) return -1; // 不存在整数解
else return (int) (1L * x * (b / d) % n);
}
乘法逆元(模逆元)
定义
若整数
作用
在要除以一个数,再取模时,把除法变成乘法运算,然后再取模。
快速幂求逆元
只能在模数为质数的情况下使用。
根据逆元的定义,有:
两边同乘
所以:
根据费马小定理 :若
从上式
结合①式得到:
综上,在
扩展欧几里得算法求逆元
根据逆元的定义,有:
两边同乘
所以:
设
模板代码:
int x, y;
int exgcd(int a, int b) {
if (b == 0) {
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b);
int t = x;
x = y;
y = t - a / b * y;
return d;
}
// a在模n意义下的逆元
int inv(int a, int n) {
exgcd(a, n);
return (x % n + n) % n;
}
int x, y;
int exgcd(int a, int b) {
if (b == 0) {
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b);
int t = x;
x = y;
y = t - a / b * y;
return d;
}
// a在模n意义下的逆元
int inv(int a, int n) {
exgcd(a, n);
return (x % n + n) % n;
}
组合数
从
组合数计算公式:
特别地,规定当
递推求组合数
模板题:求组合数 I。
原理:
证明:
在
- 如果该物品不在要选择的
个物品中,那么我们还需要在 个物品中选择 个物品( ); - 如果该物品在要选择的
个物品中,那么我们还需要在 个物品中选择 个物品( )。
综上,
适用数据范围:
模板代码:
static final int N;
static int[][] c = new int[N][N];
static void comb() {
for (int n = 0; n < N; ++n) {
for (int m = 0; m <= n; ++m) {
if (m == 0) c[n][m] = 1;
else c[n][m] = c[n - 1][m] + c[n - 1][m - 1];
}
}
}
// ans: c[a][b]
static final int N;
static int[][] c = new int[N][N];
static void comb() {
for (int n = 0; n < N; ++n) {
for (int m = 0; m <= n; ++m) {
if (m == 0) c[n][m] = 1;
else c[n][m] = c[n - 1][m] + c[n - 1][m - 1];
}
}
}
// ans: c[a][b]
预处理阶乘求组合数
模板题:求组合数 II。
原理:
适用数据范围:
模板代码:
// MOD必须是质数才能用快速幂求逆元
static final int N, MOD;
// fact[i]表示i的阶乘(% MOD) infact[i]表示i的阶乘的逆元(% MOD)
static int[] fact = new int[N], infact = new int[N];
// 预处理1~N的阶乘及其逆元
static {
fact[0] = 1;
for (int i = 1; i < N; ++i) {
fact[i] = (int) (1L * fact[i - 1] * i % MOD);
infact[i] = (int) qmi(fact[i], MOD - 2, MOD);
}
}
// 快速幂 用作求逆元
static long qmi(long base, long exp, long mod) {
long res = 1L;
while (exp > 0) {
if ((exp & 1) == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp >>= 1;
}
return res;
}
static int C(int n, int m) {
return n == m ? 1 : (int) (1L * fact[n] * infact[m] % MOD * infact[n - m] % MOD);
}
// ans: C(a, b)
// MOD必须是质数才能用快速幂求逆元
static final int N, MOD;
// fact[i]表示i的阶乘(% MOD) infact[i]表示i的阶乘的逆元(% MOD)
static int[] fact = new int[N], infact = new int[N];
// 预处理1~N的阶乘及其逆元
static {
fact[0] = 1;
for (int i = 1; i < N; ++i) {
fact[i] = (int) (1L * fact[i - 1] * i % MOD);
infact[i] = (int) qmi(fact[i], MOD - 2, MOD);
}
}
// 快速幂 用作求逆元
static long qmi(long base, long exp, long mod) {
long res = 1L;
while (exp > 0) {
if ((exp & 1) == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp >>= 1;
}
return res;
}
static int C(int n, int m) {
return n == m ? 1 : (int) (1L * fact[n] * infact[m] % MOD * infact[n - m] % MOD);
}
// ans: C(a, b)
卢卡斯定理求组合数
模板题:求组合数 III。
原理:
- 若
,那么直接从定义出发,使用公式求解:
- 否则代入 Lucas 公式 :
递归计算。
适用数据范围:
模板代码:
// 快速幂 用作且求逆元
static long qmi(long base, long exp, long mod) {
long res = 1L;
while (exp > 0) {
if ((exp & 1) == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp >>= 1;
}
return res;
}
// 求组合数
static long C(long n, long m, long p) {
long nume = 1L, deno = 1L;
for (long a = n, b = 1; b <= m; --a, ++b) {
nume = nume * a % p;
deno = deno * b % p;
}
return nume * qmi(deno, p - 2, p) % p;
}
// 卢卡斯定理
static long lucas(long n, long m, long p) {
if (n < p && m < p) return C(n, m, p);
else return lucas(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}
// ans: lucas(a, b, p)
// 快速幂 用作且求逆元
static long qmi(long base, long exp, long mod) {
long res = 1L;
while (exp > 0) {
if ((exp & 1) == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp >>= 1;
}
return res;
}
// 求组合数
static long C(long n, long m, long p) {
long nume = 1L, deno = 1L;
for (long a = n, b = 1; b <= m; --a, ++b) {
nume = nume * a % p;
deno = deno * b % p;
}
return nume * qmi(deno, p - 2, p) % p;
}
// 卢卡斯定理
static long lucas(long n, long m, long p) {
if (n < p && m < p) return C(n, m, p);
else return lucas(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}
// ans: lucas(a, b, p)
高精度+质因数分解求组合数
模板题:求组合数 IV。
原理:
根据公式:
,分别把 质因数分解,对于 中的每个质数,维护 表示: 质因数分解后 的次数 质因数分解后 的次数 质因数分解后 的次数。最后把 中有次数的质数按照其次数乘起来就能得到答案; 快速计算
的阶乘的质因数分解中质因子 的数量:勒让德定理 ; 高精度 Java 用
BigInteger
包。
适用数据范围:
// 预处理筛出1~N之间的所有质数
static final int N;
static int[] primes = new int[N];
static int cnt = 0;
static boolean[] np = new boolean[N + 1];
// 线性筛
static {
for (int x = 2; x <= N; ++x) {
if (!np[x]) primes[cnt++] = x;
for (int i = 0; primes[i] <= N / x; ++i) {
np[primes[i] * x] = true;
if (x % primes[i] == 0) break;
}
}
}
// 求n!中因子p的数量
static int count(int n, int d) {
int cnt = 0;
while (n > 0) cnt += n /= d;
return cnt;
}
static BigInteger C(int n, int m) {
BigInteger res = BigInteger.valueOf(1);
for (int i = 0; i < cnt; ++i) {
int p = primes[i];
// 求每个质因子的幂次数
int cnt = count(n, p) - count(m, p) - count(n - m, p);
BigInteger bp = BigInteger.valueOf(p);
while (cnt-- > 0) res = res.multiply(bp);
}
return res;
}
// ans: C(a, b)
// 预处理筛出1~N之间的所有质数
static final int N;
static int[] primes = new int[N];
static int cnt = 0;
static boolean[] np = new boolean[N + 1];
// 线性筛
static {
for (int x = 2; x <= N; ++x) {
if (!np[x]) primes[cnt++] = x;
for (int i = 0; primes[i] <= N / x; ++i) {
np[primes[i] * x] = true;
if (x % primes[i] == 0) break;
}
}
}
// 求n!中因子p的数量
static int count(int n, int d) {
int cnt = 0;
while (n > 0) cnt += n /= d;
return cnt;
}
static BigInteger C(int n, int m) {
BigInteger res = BigInteger.valueOf(1);
for (int i = 0; i < cnt; ++i) {
int p = primes[i];
// 求每个质因子的幂次数
int cnt = count(n, p) - count(m, p) - count(n - m, p);
BigInteger bp = BigInteger.valueOf(p);
while (cnt-- > 0) res = res.multiply(bp);
}
return res;
}
// ans: C(a, b)
卡特兰数
模板题:满足条件的01序列。