- 前言
关于n!的一个小问题 -如何求n!有多个p;
最直接的
int cal(int n, int p) //计算n!有多少个p
{
int ans = 0;
for (int i = 2; i <= n; i++) {
int temp = i;
while (temp%p == 0) ans++, temp /= p;
}
return ans;
}
当n>18的时候会无法承受,
另一种方法
n!中有(n/p+n/p2+……)个p因子
int cal(int n, int p) {
int ans = 0;
while (n) {
ans += n / p;
n /= p; }
return ans;
}
递归写法
int cal(int n, int p) {
if (n < p) return 0;
else return n / p + cal(n / p, p);
}
- 问题一:如何计算组合数Cmn
直接利用公式
ll C(ll n, ll m) {
ll ans = 1;
for (ll i = 1; i <= n; i++) ans *= i;
for (ll i = 1; i <= m||i<=n-m; i++)
if (i <= n - m) ans /= (i*i);
else ans / i;
return ans;
}
第二种递推公式
C(n,m)=C(n-1,m)+C(n-1,m-1);
ll C(ll n, ll m) {
if (m == 0 || m == n) return 1;
return C(n - 1, m) + C(n - 1, m - 1);
}
记忆化搜索
ll res[67][67] = { 0 };
ll C(ll n, ll m) {
if (m == 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);
}
打表
void C(ll n) {
for (int i = 0; i <= n; i++)
res[i][0] = res[i][i] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i / 2; j++){
res[i][j] = res[i - 1][j] + res[i - 1][j - 1];
res[i][i - j] = res[i][j];
}
}
}
方法三: 通过定义式
ll C(ll n, ll m) {
ll ans = 0;
for (ll i = 1; i <= m; i++)
ans *= (n - m + i) / i;
return ans;
}
- 问题二:如何取模P
对于不大的n直接使用前两种方法取模即可
现在介绍另外两种方法
1 如果m<p,并且p是素数
int C(int n, int m, int p) {
int ans = 1;
for (int i = 1; i <= m; i++) {
ans = ans * (n - m + 1) % p;
ans = ans * inv(i, p) % p; //inv函数求在模p的情况下i的逆元
}
return ans;
}
2
int C(int n, int m, int p) {
int ans = 1, nump = 0;
for (int i = 1; i <= m; i++) {
int temp = n - m + i;
while (temp%p == 0) nump++, temp /= p;
ans = ans * temp%p;
temp = i;
while (temp%p == 0) nump--, temp /= p;
ans = ans * inv(temp, p) % p;
}
if (nump > 0) return 0;
else return ans;
}
3 lucas 定理
int lucas(int n, int m) {
if (m == 0) return 1;
return C(n%p, m%p)*lucas(n / p, m / p) % p; //搭配之前的计算C的函数
}
到现在为止,我们说的都是p为素数的情况,那么P不为素数应该怎么算呢;
我们将p分解为几个质因子相乘的情况,分别求模并存入a数组,质因子的阶乘存入m数组中,最后用中国剩余定理求解。完成!!!1
先预处理 素数表;
int n;
void seize(int n) { //筛掉素数
int m = sqrt(n) + 0.5;
memset(vis, 0, sizeof(vis));
for (int i = 2; i <= m; i++) //因子最大不超过sqrt(n),以其为界
if (!vis[i]) {
for (int j = i * i; j <= n; j += i) //最小质因子的倍数一定是合数;
vis[j] = 1;
}
}
void get_prime(int n)
{
seize(n);
int c = 0;
for (int i = 2; i < n; i++) {
if (!vis[i]) prime[c++] = i;
}
}
中国剩余定理
void exgcd(ll a, ll b, ll &r, ll x, ll y) {
if (!r) {
x = 1, y = 0;
r = a;
}
else {
exgcd(b, a%b, r, y, x);
y = y - x * (a / b);
}
}
ll china(ll n, ll *a, ll *m) {
ll M = 1, v, y, d, x = 0;
for (int i = 0; i < n; i++) M *= m[i];
for (int i = 0; i < n; i++) {
ll w = M / m[i];
exgcd(m[i], w, d, d, y);
x = (x + y * w*a[i]) % M;
}
return (x + M) % M;
}
现在的问题就是求a,m数组
ll pow(int n, int a) { //n^a
if (a == 0) return 1;
if (a == 1) return n;
ll ans = pow(n, a / 2);
ans *= ans;
if (ans & 1) ans *= n;
return ans;
}
ll inv(ll a, ll n) { //求逆
ll d, x, y;
exgcd(a, n, d, x, y);
if (d == 1) return (x + n) % n;
else return -1;
}
ll cal(int n,int m,int p,int a) { //求出C(n,m)mod p注意p现在仍然有可能是素数
ll ans = 1, nump = 0,temp0=1,temp1=1;
for (int i = 1; i <= m; i++) {
temp0 = n-m+i;
while (temp0%p) nump++, temp0 /= p;
temp1 = i*temp1;
while (temp1%p==0) nump--, temp1 /= p;
ans = ans * temp0%pow(p,a);
if (nump >= 0) { ans *= inv(temp1, pow(p, a)) % pow(p, a); temp1 = 1; }
}
//nump 最后一定大于0,如果不大于0就证明分母上有消除不了的因子,
//如此的化就证明这个数是小数,自然也就不存在取模问题了
return ans;
}
ll a[maxn], mm[maxn];
ll c(int n, int m, int p) {
int tot = 0;
for (int i = 0; prime[i] <= p; i++) {
int val = prime[i], x = 0;
if (p%val == 0) {
while (p%val == 0) x++, p /= val;
a[tot] = cal(n, m, val, x);
mm[tot++] = pow(val, x);
}
}
int ans = china(tot, a, mm);
return ans;
}