Educational Codeforces Round 83 (Rated for Div. 2)的D题所用到的Lucas定理,小编看了多篇博文后总结了一下,他其实是求组合数的一种方法,求组合数总共有以下几种方法。
1.当数据范围较小时,差不多1000左右,直接用递归打表求,这里用到原理就是从n个球取m个球,相当于(取第n个,然后从前n-1个再取m-1个 + 不取第n个,从前n-1个取m个)即c[i][j] = (c[i - 1][j] + c[i - 1][j - 1])
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;
2.当数据范围中等,差不多是10e5 ,可以用逆元求解,为什么能用逆元求解,小编懒得码字了,大家百度一下就行,原理也很简单。
预处理出所有阶乘取模的余数fact[N]和所有阶乘取模的逆元infact[N],这里用到的公式就是C(a,b)=a! / b!(a-b)! ,这样的话相当于a! * 逆元。,当p是质数的情况下直接用费马小定理求解。
int qmi(int a, int k, int p) // 快速幂模板
{
int res = 1;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
// 预处理阶乘的余数和阶乘逆元的余数
fact[0] = 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;
}
//结果=fact[a]*infact[b]
当数据范围到1e9的时候,这时候就用到Lucas定理了。
内容:
C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
别问怎么证,问就是不会,代码也简单 y总板子
这里值得注意的就是,mod同样不能太大
//若p是质数,则对于任意整数 1 <= m <= n,有:
// C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
int qmi(int a, int k) // 快速幂模板
{
int res = 1;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
int C(int a, int b) // 通过定理求组合数C(a, b)
{
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;
}
return res;
}
int lucas(LL a, LL b)
{
if (a < p && b < p) return C(a, b);
return (LL)C(a % p, b % p) * lucas(a / p, b / p) % p;
}
上面都是取模一个数,假如不取模的时候还要去求C(n,m)时候,就用到了分解质因数法求组合数.板子还是用y总的,链接见上面。
/*当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用:
1. 筛法求出范围内的所有质数
2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。 n! 中p的次数是 n / p + n / p^2 + n / p^3 + ...
3. 用高精度乘法将所有质因子相乘*/
int primes[N], cnt; // 存储所有质数
int sum[N]; // 存储每个质数的次数
bool st[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;
}
}
}
int get(int n, int p) // 求n!中的次数
{
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;
}
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]);
总的来说,Lucas定理还是挺简单的,一学就会一用就废.