Typesetting math: 100%

组合数计算

(nm)=n!(nm)!m!

1 .定义法求组合数

Code

copy
LL C(LL a,LL b,LL MOD) { if(b>a) return 0; LL res=1; for(LL i=1,j=a;i<=b;i++,j--) { res=res*j%MOD; res=res*quickpow(i,MOD-2,MOD)%MOD; } return res%MOD; }

时间复杂度:O(m)

2 .递推法求组合数

Code

copy
LL c[N][N]; void init() { c[0][0]=1; for(int i=1;i<=2000+3;i++) for(int j=0;j<=i;j++) { if(j) c[i][j]=(c[i][j]+c[i-1][j-1])%MOD; c[i][j]=(c[i][j]+c[i-1][j])%MOD; } }

时间复杂度:O(nm)O(1).

3 .阶乘逆元法求组合数

Code

copy
LL power(LL x,LL k,LL MOD) { LL res=1; x%=MOD; while(k) { if(k&1) res=res*x%MOD; x=x*x%MOD; k>>=1; } return res%MOD; } inline LL C(LL n, LL m) { return fact[n]*power(fact[n-m]*fact[m],MOD-2,MOD)%MOD; }

预处理部分

copy
fact[0]=1; for(LL i=1;i<N;i++) fact[i]=fact[i-1]*i%MOD;

时间复杂度:O(n)O(logn).
如果预处理时同时处理逆元的话,
可以做到 O(nlogn)O(1).

4 .lucas定理求组合数

只适用于模质数的情况
设预处理时间为 f(p),单次求组合数的时间为 g(p)
时间复杂度为 O(f(p)+g(p)logpm)

故一般要求模数要小。

Code

copy
typedef long long LL; const int N=2e5+5; LL power(LL x,LL k,LL MOD) { LL res=1; x%=MOD; while(k) { if(k&1) res=res*x%MOD; x=x*x%MOD; k>>=1; } return res%MOD; } LL fact[N],infact[N]; LL C(int n,int m,LL p) { if(n<m) return 0; // 注意这个特判。 else return fact[n]*infact[n-m]%p*infact[m]%p; } LL lucas(int n,int m,LL p) { if(n<p && m<p) return C(n,m,p); else return C(n%p,m%p,p)*lucas(n/p,m/p,p)%p; }

预处理部分

copy
fact[0]=infact[0]=1; for(int i=1;i<=p;i++) { fact[i]=fact[i-1]*i%p; infact[i]=power(fact[i],p-2,p); }

y 总原来那个复杂度好像不大对。
目前这个是 O(plogp+logpm)
如果只递推阶乘,现算逆元的话可以 O(p+(logpm)(log2p))

Code:

copy
LL power(LL x,LL k,LL MOD) { LL res=1; x%=MOD; while(k) { if(k&1) res=res*x%MOD; x=x*x%MOD; k>>=1; } return res%MOD; } LL fact[N]; LL C(int n,int m,LL p) { if(n<m) return 0; else return fact[n]*power(fact[n-m],p-2,p)%p*power(fact[m],p-2,p)%p; } LL lucas(int n,int m,LL p) { if(n<p && m<p) return C(n,m,p); else return C(n%p,m%p,p)*lucas(n/p,m/p,p)%p; }
copy
fact[0]=1; for(int i=1;i<=p;i++) fact[i]=fact[i-1]*i%p;

5. 扩展卢卡斯定理求组合数 exlucas

P4720 【模板】扩展卢卡斯
题解

要求模的数较小,不过可以不是质数。

n,m1018,p106

Code:

copy
typedef long long LL; LL muler(LL x,LL k,LL MOD) { LL res=0; x=(x%MOD+MOD)%MOD; k=(k%MOD+MOD)%MOD; while(k) { if(k&1) res=(res+x)%MOD; x=(x+x)%MOD; k>>=1; } return res%MOD; } LL power(LL x,LL k,LL MOD) { LL res=1; x%=MOD; while(k) { if(k&1) res=muler(res,x,MOD); x=muler(x,x,MOD); k>>=1; } return res%MOD; } LL exgcd(LL a,LL b,LL& x,LL& y) { if(b==0) { x=1; y=0; return a; } LL z=exgcd(b,a%b,y,x); y-=a/b*x; return z; } LL inv(LL x,LL p) { LL y,z; exgcd(x,p,y,z); return (y%p+p)%p; } LL excrt(int n,LL b[],LL a[]) { LL m=a[1],ans=b[1]; for(int i=2;i<=n;i++) { LL y,z,d=exgcd(m,a[i],y,z); if((b[i]-ans)%d!=0) return -1; y=muler(y,(b[i]-ans)/d,a[i]/d); ans+=y*m; m=a[i]/d*m; ans=(ans%m+m)%m; } return ans; } LL divide_p(LL n,LL p,LL pk) { if(!n) return 1; LL res=1; for(LL i=1;i<=pk;i++) if(i%p) res=res*i%pk; res=power(res,n/pk,pk); for(LL i=n%pk;i>=1;i--) if(i%p) res=res*i%pk; return res*divide_p(n/p,p,pk)%pk; } LL Getp(LL n,LL p) { LL res=0; while(n) res+=n/p,n/=p; return res; } LL C(LL n,LL m,LL p,LL pk) { return divide_p(n,p,pk)*inv(divide_p(n-m,p,pk),pk)%pk *inv(divide_p(m,p,pk),pk)%pk *power(p,Getp(n,p)-Getp(n-m,p)-Getp(m,p),pk)%pk; } LL exlucas(LL n,LL m,LL p) { static LL A[1024],B[1024]; if(m>n) return 0; int tot=0; LL t=p; for(LL i=2;i*i<=t;i++) { if(t%i==0) { A[++tot]=1; for(;t%i==0;t/=i) A[tot]*=i; B[tot]=C(n,m,i,A[tot]); } } if(t>1) A[++tot]=t,B[tot]=C(n,m,t,t); return excrt(tot,B,A); }

6.阶乘分解求组合数

适用于 没有模数 或者 模数是合数并且 n,m 较小的情况。

n,m106,p1018

高精度 Code:

copy
vector<int> mul(vector<int> a,int b) { vector<int> c; int t=0,i; for(i=0;i<(int)a.size();i++) { t=t+a[i]*b; c.push_back(t%10); t/=10; } while(t>0) { c.push_back(t%10); t/=10; } return c; } const int N=10010; int p[N],tot=0; bool tag[N]; void primes(int n) { int i,j; for(i=2;i<=n;i++) { if(!tag[i]) p[++tot]=i; for(j=1;j<=tot && p[j]*i<=n;j++) { tag[i*p[j]]=true; if(i%p[j]==0) break; } } return; } int get(int n,int p) { int cnt=0; while(n>=p) cnt+=n/p,n/=p; return cnt; } vector<int> C(int n,int m) { int cnt; vector<int> c; c.push_back(1); for(int j=1;j<=tot && p[j]<=n;j++) { cnt=get(n,p[j])-get(m,p[j])-get(n-m,p[j]); while(cnt--) c=mul(c,p[j]); } return c; }

时间复杂度: O(log2C(n,m)+n).
可以近似认为是 O(n2).
时间复杂度分析:

  • 质因数分解复杂度 O(n)
  • C(n,m) 共有 lgC(n,m) 位,因此每次乘法mul需要 O(lgC(n,m))
  • C(n,m) 最多有 log2C(n,m) 个质因子,因此需要mul log2C(n,m)

综上,时间复杂度: O(log2C(n,m)+n).

如果模的是合数的话吧高精度去掉就可以了,时间复杂度 O(n)O(nlnn)

materials

posted @   cjlworld  阅读(145)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 本地部署 DeepSeek:小白也能轻松搞定!
· 传国玉玺易主,ai.com竟然跳转到国产AI
· 自己如何在本地电脑从零搭建DeepSeek!手把手教学,快来看看! (建议收藏)
· 我们是如何解决abp身上的几个痛点
· 如何基于DeepSeek开展AI项目
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起