前奏:统计 n! 中的所有质因子中pi的个数
普通方法:复杂度O(nlogn), 当n为10的18次方无法承受
// 复杂度O(nlogn), n为10的18次方无法承受
int cal(int n, int p){
int ans = 0;
for (int i = 2; i <= n; i++){
int temp = i;
while (temp % p == 0){
ans++;
temp /= p; // temp除以p
}
}
return ans;
}
改进后的方法:复杂度只有O(logn)
int cal(int n, int p){
int ans = 0;
while (n){
ans += n / p; // 累加 n / p^k
n /= p; // 相当于分母多乘一个 p
}
return ans;
}
1. 组合数的计算(如果只需计算一个组合数则用法三,如果计算很多个组合数则用法二)
法一:通过定义计算
long long C(long long n, long long m){
long long ans = 1;
for (long long i = 1; i <= n; i++){
ans *= i;
}
for (long long i = 1; i <= m; i++){
ans /= i;
}
for (long long i = 1; i <= n - m; i++){
ans /= i;
}
return ans;
}
法二:通过递推式计算
int res[100][100]; // 用于存放组合数的值
// 法二:通过递推式计算
long long C(long long n, long long m){
if (n == 0 || m == n)
return 1;
if (res[n][m] != 0)
return res[n][m];
return res[n][m] = C(n - 1, m) + C(n - 1, m - 1); // 赋值给res[n][m]并返回
}
把所有组合数都计算出来
// 把所有组合数都计算出来
const int n = 60;
void calC(){
for (int i = 0; i <= n; i++){
res[i][0] = res[i][i] = 1; // 初始化边界
}
for (int i = 2; i <= n; i++){
for (int j = 0; j <= i / 2; j++){
res[i][j] = res[i - 1][j] + res[i - 1][j - 1]; // 递推计算C(i, j)
res[i][i - j] = res[i][j]; // C(i, i - j) = C(i, j)
}
}
}
法三:定义变形,复杂度O(m)(推荐)
long long C(long long n, long long m){
long long ans = 1;
for (int i = 1; i <= m; i++){
ans = ans * (n - m + i) / i; // 注意一定要先乘再除
}
}
2. 计算C(n, m) % p
递归
// 递归
int res[1010][1010] = { 0 };
int C(int n, int m, int p){
if (m == 0 || m == n) return 1;
if (res[n][m] != 0)
return res[n][m]; // 已经有值
return res[n][m] = (C(n - 1, m, p) + C(n - 1, m - 1, p)) % p; // 赋值后返回
}
非递归
void CalC(int n, int p){
for (int i = 0; i <= n; i++){
res[i][0] = res[i][i] = 1; // 初始化边界
}
for (int i = 2; i <= n; i++){
for (int j = 0; j <= i; j++){
res[i][j] = (res[i - 1][j] + res[i - 1][j - 1]) % p;
res[i][i - j] = res[i][j];
}
}
}