OI中组合数的若干求法与CRT
ButterflyDew · · 算法·理论
OI中组合数的若干求法与CRT
只是下决心整理一下子呢~
说明:本篇文章采用
一般性的组合数求法
计算式:
\binom{m}{n}=\frac{m!}{n!\times (m-n)!}
一、 \color{red} \text{杨辉三角法}
\binom{m}{n}=\binom{m-1}{n}+\binom{m-1}{n-1}
证明可以从计算式或者组合意义入手,不过多赘述了。
一般情况下,这个适用性并不是很广,但打起来还是很方便的,也不容易错。
值得一提的是,在有关容斥的一些证明中,经常用到这个式子消去一些项,比如证明
复杂度
O(n^2)\sim O(1) ,对p 无要求
二、 \color{red} \text{预处理阶乘和阶乘逆元法}
根据计算式得到
\binom{m}{n}=fac_m\times invf_n \times invf_{m-n}
我们事先预处理阶乘及阶乘逆元,关于预处理阶乘逆元,有
invf_{i} \equiv invf_{i+1}\times (i+1) \pmod p
证明:
fac_i \times invf_i \equiv 1 \pmod p
fac_{i-1}\times i \times invf_i \equiv 1 \pmod p
只需要算一个就可以了
复杂度
O(n+\log p)\sim O(1) ,p 为质数
这里
麻烦一些的组合数求法
三、 \color{red} \tt{Lucas}\text{定理}
证明(有兴趣的可以看一下):
设
取
引理:
①
②当
则上式
整理得
对左边这样展开的这一项
而右边当且仅当
即
用法上,这个要配合阶乘和阶乘逆元完成,当
这个定理其实用处不是很大,但是实现和形式上比较简单,所以可以按实际情况考虑使用。它还有一个优点,有时候可以避免
复杂度
O(p+\log p)\sim O(\log n) ,p 为质数
四、 \color{red} \tt{lucas}\text{定理}+\tt{CRT}\text{合并}
先说说前置的
考虑两个同余方程
设
将
此时解
由裴蜀定理等,若
则
其实这个就是
按理说
中国剩余定理
对同余方程组
设
则方程组有通解
理解起来不难,对
两个方法的复杂度是一样的,均为
O(n\log p) ,但实际上普通的\tt{CRT} 用的更多一些,注意有时候避免乘起来爆\text{long long} 要用龟速乘
把前置技能点上了以后,就可以说一下方法
求
对
显然我们对每一个
但是发现模数不是质数的情况可能无解,因此实际上这种方法的局限性很大。
当题目已经给定模数而不是输入模数时,可能会用的到,比如古代猪文这个题。
为什么又强调
这个方法的复杂度其实不好具体计算,但跑起来大概像这个
O(\max p_i)\sim O(\log p)
五、 \color{red} \tt{exLucas}
其实这个东西挺麻烦的,能不写应该不会去写这玩意儿。
首先这个东西跟
对
像四一样,考虑分解以后再合并
因为
首先,我们考虑
理解起来很简单,先把所有
然后我们求剩下来的除掉所有
因为
为了方便,我们依然不计算
这个的求阶乘复杂度算起来其实是一个等比数列,是
O(n) 的
然后剩下的计算对求出来的东西求逆元并和剩下的
发现这个东西的复杂度中有一堆并不重要的
\log ,比如\tt{CRT} ,逆元什么的。真正复杂度瓶颈在于求阶乘的那个O(n) ,可以近似的认为复杂度是O(\max p_i^{c_i}) 的。
贴一下我写的代码吧,写的不好看轻喷
#include <cstdio>
#define ll long long
ll mod;
void exgcd(ll &x,ll &y,ll a,ll b)
{
if(!b){x=1,y=0;return;}
exgcd(x,y,b,a%b);
ll tmp=x;
x=y;
y=tmp-a/b*y;
}
ll inv(ll a,ll p)
{
ll x,y;
exgcd(x,y,a,p);
return x;
}
ll quickpow(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 tp)
{
if(!n) return 1;
ll f=1,res=1;
for(ll i=1;i<tp;i++)
{
if(i%p) (f*=i)%=tp;
if(i==n%tp) res=f;
}
f=quickpow(f,n/tp,tp);
return fac(n/p,p,tp)*f%tp*res%tp;
}
ll C(ll m,ll n,ll p,ll tp)
{
ll ct=0;
for(ll i=m;i;i/=p) ct+=i/p;
for(ll i=n;i;i/=p) ct-=i/p;
for(ll i=m-n;i;i/=p) ct-=i/p;
return fac(m,p,tp)*inv(fac(n,p,tp),tp)%tp*inv(fac(m-n,p,tp),tp)%tp*quickpow(p,ct,tp)%tp;
}
ll CRT(ll m,ll n,ll p,ll tp)
{
return C(m,n,p,tp)*(mod/tp)%mod*inv(mod/tp,tp)%mod;
}
ll exlucas(ll m,ll n,ll p)
{
ll ans=0;
for(ll i=2;i*i<=p;i++)
{
if(p%i==0)
{
ll tp=1;
while(p%i==0) tp*=i,p/=i;
(ans+=CRT(m,n,i,tp))%=mod;
}
}
if(p!=1) (ans+=CRT(m,n,p,p))%=mod;
return (ans%mod+mod)%mod;
}
int main()
{
ll n,m;
scanf("%lld%lld%lld",&n,&m,&mod);
printf("%lld\n",exlucas(n,m,mod));
return 0;
}
六、 \color{red} \text{一些灵活一点的应用}
-
给定一个长为
n(n\le 3\times10^5) 的有重序列,求这个序列在所有排列中的排名,对p(p\le 2\times 10^9) 取模其实就是有重复元素的排列问题。
像康托展开那样,讨论每一位的贡献。
不妨设当前处理到第
i 位的子问题了,令a_1,a_2,\dots,a_m 代表从第i 位到第n 位的排列,b_1,b_2,\dots,b_m 代表i 到第n 位的第一个排列,c_i 代表数值为i 的数字的出现次数。若
a_1=b_1 ,则当前位无贡献,处理下一位的子问题。若
a_1>b_1 ,则计算所有小于a_1 的数值排在第一位后,后面的全排列,即为
这里
后面的求和用树状数组维护,前面的删一个维护一个就可以了。
发现
-
给出
n(n\le 2\times 10^5) 个质数,求这些质数乘积的约数之积模10^9+7 设乘积
N=\prod\limits_{i=1}^kp_i^{c_i} ,则约数个数d=\prod\limits_{i=1}^kc_i+1 ,那么答案为
用扩展欧拉定理求一下指数,即
把
-
有
n(n\le 20) 个集合,第i 个集合有c_i(0\le c_i \le 10^{12}) 个相同元素,不同集合的元素互不相同,从中取出m(0\le m\le 10^{14}) 个元素,问有多少种取法,对10^9+7 取膜其实就是求多重集的组合数,设有多重集
\{x_1\cdot c_1,x_2\cdot c_2,\dots,x_k\cdot c_k\} ,x 是元素的值,而c 是这个值的个数,设C=\sum\limits_{i=1}^k c_i 。首先我们不考虑每种元素的上限时(也就是当每种元素都有无限个的时候)的方案数
从组合的意义出发,相当于把
n 个元素有序的划分成k 组,每组可以为空的方案数,即为
考虑
则至少某一种元素取多了的方案数为
类比可以得到至少两种元素取多了的方案数等等
套用容斥原理了,即为
上式代表的是至少某一种元素取多了的方案数,注意这一步的“至少”和之前的意义并不一样。
则总方案为无限制的多重集的总排列数减去至少某一个元素多取了个数的方案数。
在计算这个题的组合数时,发现
处理一下底下的逆元直接爆算就可以了。
最后一点发现要用龟速乘,不过发现也可以先用
题目地址
PER-Permutation
没找到。
CF451E Devu and Flowers