线性求逆元模板_模板 - 组合数学 - (新)

其实一般都只是求一个组合数:

const ll MOD=1e9+7;

const int MAXN=1e6;

ll inv[MAXN+5],fac[MAXN+5],invfac[MAXN+5];

void init_inv(int n=MAXN,ll mod=MOD) {

inv[1]=1;

for(int i=2; i<=n; i++) {

inv[i]=inv[mod%i]*(mod-mod/i)%mod;

}

}

void init_fac_invfac(int n=MAXN,ll mod=MOD) {

init_inv(n);

fac[0]=1,invfac[0]=1;

for(int i=1; i<=n; i++) {

fac[i]=fac[i-1]*i%mod;

invfac[i]=invfac[i-1]*inv[i]%mod;

}

}

inline ll C(ll n,ll m,ll mod=MOD) {

if(n

return 0;

return fac[n]*invfac[n-m]%mod*invfac[m]%mod;

}

相关知识点应在《组合数学》中寻找,而不是在模板中寻找。

从旧模板的快速幂bug,逆元bug,排列数bug一路走来……

update1:通过【模板】卢卡斯定理的验证。

update2:优化了直接计算组合数的速度,(可能)优化了卢卡斯定理的退出条件。

update3:增加了错位排序,D(n)表示n个数的排列个数,使得每个数都不在应在的位置,即A[i]!=i对所有i成立。

update4:当模数比较小时,可能出现前缀积为0的情况,这种时候导致乘法逆元并不存在。会使得使用线性乘法逆元的组合数失效!但是卢卡斯定理可以正确约分掉!例如p=10007时!

//特殊定义D[0]为1

D[0]=1;

D[1]=0;

for(int i=2;i<=1000000;i++){

if(i&1){

D[i]=((ll)i*D[i-1]-1ll)%MOD;

if(D[i]<0)

D[i]+=MOD;

}

else{

D[i]=((ll)i*D[i-1]+1ll)%MOD;

}

}

二项式反演

可以表示成

\(f_n=\sum\limits_{i=0}^{n}(−1)^iC_n^ig_i⇔g_n=\sum\limits_{i=0}^{n}(−1)^iC_n^if_i\)

你会发现这个式子具有极强的对称性!另外一个更加常见的形式是

\(f_n=\sum\limits_{i=0}^{n}C_n^ig_i⇔g_n=\sum\limits_{i=0}^{n}(−1)^{n-i}C_n^if_i\)

注意事项:

1.一些函数需要修改常量以及初始化

2.不用到初始化时应回收空间,设MAXN=0即可。

标准模板,卢卡斯定理默认装载重新求组合数。

namespace combinatorics{

//注意需要init(),必要时修改常量

const ll MOD=1e9+7;

const int MAXN=2000000;

ll inv[MAXN+5],fac[MAXN+5],invfac[MAXN+5];

//1. 快速幂 x^n %mod

inline ll qpow(ll x,ll n,ll mod=MOD) {

ll res=1%mod;

while(n) {

if(n&1)

res=res*x%mod;

x=x*x%mod;

n>>=1;

}

return res;

}

//2. 快速乘 a*b %mod 防止乘法溢出ll

inline ll qmut(ll a,ll b,ll mod=MOD) {

ll res=0;

while(b) {

if(b&1)

res=(res+a)%mod;

a=(a+a)%mod;

b>>=1;

}

return res;

}

//3. 乘法逆元 快速幂+费马小定理,要求p必须是质数 (依赖1. 快速幂)

inline ll inv_p(ll n,ll p=MOD) {

return qpow(n,p-2,p);

}

//4. 扩展欧几里得算法:返回 g=gcd(a,b) ,以及对应的等式 ax+by=g 的解

ll exgcd(ll a,ll b,ll &x,ll &y) {

if(!a&&!b)

return -1;

if(!b) {

x=1,y=0;

return a;

}

ll d=exgcd(b,a%b,y,x);

y-=a/b*x;

return d;

}

//5. 扩展欧几里得算法求逆元,只要求 a,m 互质

inline ll inv_rp(ll a,ll mod=MOD) {

ll x,y;

ll d=exgcd(a,mod,x,y);

if(d==1)

return (x%mod+mod)%mod;

return -1;

}

//6. 线性求乘法逆元

void init_inv(int n=MAXN,ll mod=MOD) {

inv[1]=1;

for(int i=2; i<=n; i++) {

inv[i]=inv[mod%i]*(mod-mod/i)%mod;

}

}

//7. 线性求阶乘,阶乘乘法逆元 (依赖6. 线性求乘法逆元)

void init_fac_invfac(int n=MAXN,ll mod=MOD) {

.//这个点用来触发编译错误,提示使用init(),并修改MAXN和MOD

init_inv(n);

fac[0]=1,invfac[0]=1;

for(int i=1; i<=n; i++) {

fac[i]=fac[i-1]*i%mod;

invfac[i]=invfac[i-1]*inv[i]%mod;

}

}

//8. 利用阶乘和阶乘逆元计算排列数A_n^m %mod (依赖7. 线性求阶乘,阶乘乘法逆元)

inline ll A(ll n,ll m,ll mod=MOD) {

return fac[n]*invfac[n-m]%mod;

}

//9. 直接计算排列数A_n^m %mod

ll A_2(ll n,ll m,ll mod=MOD){

if(m>n) return 0;

ll u=1;

for(int i=n-m+1;i<=n;i++)

u=u*i%mod;

return u;

}

//10. 利用阶乘和阶乘逆元计算组合数C_n^m %mod (依赖7. 线性求阶乘,阶乘乘法逆元)

inline ll C(ll n,ll m,ll mod=MOD) {

if(n

return 0;

return fac[n]*invfac[n-m]%mod*invfac[m]%mod;

}

//11. 直接计算组合数C_n^m %mod

inline ll C_2(ll n,ll m,ll mod=MOD){

if(n

return 0;

//组合数对称优化

m=min(m,n-m);

ll u=1,d=1;

for(int i=n-m+1;i<=n;i++)

u=u*i%mod;

for(int i=1;i<=m;i++)

d=d*i%mod;

//为下面的inv装入正确的乘法逆元,默认使用扩展欧几里得算法重新求解

//可以视情况换用更快的init()后的inv[d],或者费马小定理(当mod为质数时费马小定理更简单)

return u*inv_rp(d,mod)%mod;

}

//12. 卢卡斯定理计算组合数C_n^m%p,p是质数 (依赖10. /11. 计算组合数)

inline ll Lucas(ll n,ll m,ll p=MOD) {

if(m>n)

return 0;

ll ans=1;

//当ans为0之后就可以返回了

while(m&&ans){

//当p并非默认参数MOD时,必须使用直接计算组合数的C_2

ans=ans*C_2(n%p,m%p,p)%p;

//当p为默认参数MOD时,使用init()后的O(1)求组合数

//ans=ans*C(n%p,m%p)%p;

n/=p,m/=p;

}

return ans;

}

};

using namespace combinatorics;

//注意需要init(),必要时修改常量

标签:return,组合,res,ll,数学,MAXN,MOD,模板,mod

来源: https://www.cnblogs.com/Yinku/p/10703338.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值