组合数总结
☆组合数的若干求法☆
一、杨辉三角法
公式:\(C_m^n=C_{m-1}^n+C_{m-1}^{n-1}\)
要求:无
复杂度:\(O(N^2)\)~\(O(1)\)
适用性:较广
二、逆元法
公式:\(C_m^n=\frac{m!}{n! \times (m-n)!}\)
要求:模数\(p\)为质数
复杂度:\(O(logp+m)\)(计算逆元+预处理阶乘)
预处理后可做到:\(O(m)\)~\(O(1)\)
适用性:\(m\)较小时
三、lucas定理+逆元法
公式:
\(C_m^n=\frac{m!}{n! \times (m-n)!}\)
\(C_m^n \equiv C_{m/p}^{n/p} \times C_{m \% p}^{n \% p} \ (mod \ p)\)
要求:模数\(p\)为质数
复杂度:\(O(logmlogp+p)\)
预处理后可做到:\(O(p)\)~\(O(logm)\)
适用性:\(p\)较小时,\(m\)较大时
四、lucas定理+逆元+CRT合并法
公式同三
要求:模数分解质因数后,所有的质因子指数都为\(1\)
具体的:我们在每一个质因子下的答案,然后适用CRT进行合并
复杂度:\(O(tlogmaxplogm+tlogp)\)(maxp为p的最大质因子,t是质因子个数,后面一项是玄学,假装是CRT合并的复杂度)
预处理后:\(O(tlogmaxp)\)~\(O(tlogm)\)
适用性:小,但\(p,m,n\)可以较大且\(p\)可以不是质数
五、扩展lucas
要求:无
到这里,我们把上面所有的求法都抛开吧,不一样不一样的,别被误导了
看看最开始的东西,求
\(\frac{m!}{n! \times (m-n)!} \ mod \ p\)
先把\(p\)唯一分解掉
\(p=\prod_{i=1}^t p_i^{c_i}\)
如果我们可以求出每一个\(\frac{m!}{n! \times (m-n)!} \ mod \ p_i^{c_i}\),那么很显然是可以CRT合并的。
问题转换成了如何求
\(\frac{m!}{n! \times (m-n)!} \ mod \ p_i^{c_i}\)
我们考虑把计算式中所有含\(p_i\)的项给搞出来,然后拿到一边去
拿走的一部分直接快速幂计算,没拿走的一部分也可以直接求逆元了
我们考虑 没拿走的一部分的计算 和 含\(p_i\)的项的个数的计算公式
首先考虑\(n!\)中含有多少个\(p_i\)
设\(f(n)\)代表\(n!\)中含\(p_i\)的个数
\(n!=1 \times 2 \times 3 \times ... \times n\)
我们把乘积项中所有含\(p_i\)的项取出来,这些项一定是\(1 \times p_i\),\(2 \times p_i\)...\(\lfloor n/p_i \rfloor \times p_i\),对每一项取出一个\(p_i\),则剩下的就是\(\lfloor n/p_i \rfloor !\),很好子问题
我们得到了递归计算的公式:
\(f(x)=f(\lfloor x/p_i \rfloor)+\lfloor x/p_i \rfloor\)
\(f(0)=0\)
然后我们考虑如何计算
\(\frac{n!}{f(n)*p_i}\)
我们把乘积项按\(p_i^{c_i}\)分成一节一节的,然后把所有的\(p_i\)的倍数项拿走
因为\(x \equiv x+p_i^{c_i} \ (mod \ p_i^{c_i})\),所以每一节的乘积是一样的,快速幂直接算,剩下的不足一个循环节的暴力算
然后我们算\(p_i\)的倍数项,发现同样的,我们把每一项拿走一个,就剩下了子问题,真是皆大欢喜,问题就解决啦
分析一下复杂度
求除过以后的阶乘\(O(log_{p_i}m+p_i^{c_i})\)
求质因子个数\(O(log_{p_i}m)\)
求逆元和CRT合并的复杂度应该比较低。。
大概就是这样 实际上是算不下去啦
适用性:非常广,但是不太好打
Code:
#include <cstdio>
#define ll long long
ll mod;
ll quick_pow(ll d,ll k,ll p)
{
ll f=1;
while(k)
{
if(k&1) f=f*d%p;
d=d*d%p;
k>>=1;
}
return f;
}
ll fac(ll n,ll p,ll d)
{
if(!n) return 1;
ll f=1,res=1;
for(ll i=2;i<=p;i++)
if(i%d) (f*=i)%=p;
for(ll i=2;i<=n%p;i++)
if(i%d) (res*=i)%=p;
return fac(n/d,p,d)*quick_pow(f,n/p,p)%p*res%p;
}
void exgcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
x=1,y=0;
return;
}
exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*y;
}
ll inv(ll n,ll p)
{
ll x,y;
exgcd(n,p,x,y);
return (x%p+p)%p;
}
ll C(ll n,ll m,ll p,ll d)
{
ll ct=0;
for(ll i=m;i;i/=d) ct+=i/d;
for(ll i=n;i;i/=d) ct-=i/d;
for(ll i=m-n;i;i/=d) ct-=i/d;
return fac(m,p,d)*inv(fac(n,p,d),p)%p*inv(fac(m-n,p,d),p)%p*quick_pow(d,ct,p)%p;
}
ll CRT(ll a,ll b)
{
return mod/b*a%mod*inv(mod/b,b)%mod;
}
ll exlucas(ll n,ll m,ll p)
{
ll ans=0;
for(ll d=1,i=2;i*i<=p;d=1,i++)
{
if(p%i==0)
{
while(p%i==0)
{
p/=i;
d*=i;
}
(ans+=CRT(C(n,m,d,i),d))%=mod;
}
}
if(p!=1)
(ans+=CRT(C(n,m,p,p),p))%=mod;
return ans;
}
int main()
{
ll m,n;
scanf("%lld%lld%lld",&m,&n,&mod);
printf("%lld\n",exlucas(n,m,mod));
return 0;
}
总结:灵活使用吧,最好都掌握一下
2018.8.27