ACM数论之旅8---组合数(组合大法好(,,• ₃ •,,) )
一道组合数与全错排的公式。
组合数并不陌生(´・ω・`)


我们都学过组合数
会求组合数吗
一般我们用杨辉三角性质
杨辉三角上的每一个数字都等于它的左上方和右上方的和(除了边界)

第n行,第m个就是,就是C(n, m) (从0开始)
电脑上我们就开一个数组保存,像这样

用递推求
1 #include<cstdio>
2 const int N = 2000 + 5;
3 const int MOD = (int)1e9 + 7;
4 int comb[N][N];//comb[n][m]就是C(n,m)
5 void init(){
6 for(int i = 0; i < N; i ++){
7 comb[i][0] = comb[i][i] = 1;
8 for(int j = 1; j < i; j ++){
9 comb[i][j] = comb[i-1][j] + comb[i-1][j-1];
10 comb[i][j] %= MOD;
11 }
12 }
13 }
14 int main(){
15 init();
16 }
https://ac.nowcoder.com/acm/contest/881#question E题,另外一种求组合数。
#include<bits/stdc++.h> #define ll long long #define M (ll)(1e9+7) using namespace std; ll CM[4001]={1}; ll Pow(ll a,ll b){ //快速幂 a%=M; ll ans = 1; for(;b;b>>=1) { if(b&1) ans = (ans*a)%M; a = (a*a)%M; } return ans; } ll Quk(ll a,ll b){ //快速乘 a%=M; ll ans = 0; for(;b;b>>=1) { if(b&1) ans = (ans+a)%M; a = (a+a)%M; } return ans; } ll C(ll m,ll n){ //n>=m return Quk(Quk(CM[n],Pow(CM[n-m],M-2)),Pow(CM[m],M-2))%M; } ll A(ll m,ll n){ //n>=m return Quk(CM[n],Pow(CM[n-m],M-2))%M; } int main() { ll a,b; for(int i=1;i<4001;i++) CM[i]=Quk(CM[i-1],i); while(cin>>a>>b) { ll ans=C(a+b,2*(a+b)); if(a) ans-=C(a-1,2*(a+b)); if(b) ans-=C(b-1,2*(a+b)); cout<<(ans+2*M)%M<<endl; } return 0; }
需要mod是质数
#include<bits/stdc++.h> using namespace std; #define INF 0x3fffffff #define maxn 100005 typedef long long ll; ll n,m,k,t; const ll mod = 1e9+7; ll fac[maxn]; ll inv[maxn]; ll qpow(ll a, ll b) { ll r = 1, t = a; while (b) { if (b & 1)r = (r*t) % mod; b >>= 1;t = (t*t) % mod; } return r; } void init() { fac[0] = 1; for (int i = 1;i <= mmax;i++) fac[i] = fac[i - 1] * 1ll * i%mod; inv[mmax] = qpow(fac[mmax], mod - 2); for (int i = mmax - 1;~i;i--) inv[i] = inv[i + 1] * 1ll * (i + 1) % mod; } ll C(ll n, ll m) { if (m>n) return 0; if (m == n || m == 0) return 1; return fac[n] * 1ll * inv[n - m] % mod*inv[m] % mod; } int main(){ init(); while(~scanf("%lld%lld",&n,&m)) printf("%lld\n",(C(2*m+2*n,n+m)+mod-(C(2*m+2*n,n-1)+C(2*m+2*n,m-1))%mod)%mod); }
(PS:大部分题目都要求求余,而且大部分都是对1e9+7这个数求余)
这种方法的复杂度是O(n^2),有没有O(n)的做法,当然有(´・ω・`)
因为大部分题都有求余,所以我们大可利用逆元的原理(没求余的题目,其实你也可以把MOD自己开的大一点,这样一样可以用逆元做)
根据这个公式

我们需要求阶乘和逆元阶乘
我们就用1e9+7来求余吧
long long F[100010]; void init(long long p) { F[0] = 1; for(int i = 1;i <= p;i++) F[i] = F[i-1]*i % (1000000007); } long long inv(long long a,long long m) { if(a == 1)return 1; return inv(m%a,m)*(m-m/a)%m; } long long Lucas(long long n,long long m,long long p) { long long ans = 1; while(n&&m) { long long a = n%p; long long b = m%p; if(a < b)return 0; ans = ans*F[a]%p*inv(F[b]*F[a-b]%p,p)%p; n /= p; m /= p; } return ans; }
代码如下:
1 #include<cstdio>
2 const int N = 200000 + 5;
3 const int MOD = (int)1e9 + 7;
4 int F[N], Finv[N], inv[N];//F是阶乘,Finv是逆元的阶乘
5 void init(){
6 inv[1] = 1;
7 for(int i = 2; i < N; i ++){
8 inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
9 }
10 F[0] = Finv[0] = 1;
11 for(int i = 1; i < N; i ++){
12 F[i] = F[i-1] * 1ll * i % MOD;
13 Finv[i] = Finv[i-1] * 1ll * inv[i] % MOD;
14 }
15 }
16 int comb(int n, int m){//comb(n, m)就是C(n, m)
17 if(m < 0 || m > n) return 0;
18 return F[n] * 1ll * Finv[n - m] % MOD * Finv[m] % MOD;
19 }
20 int main(){
21 init();
22 }
本文深入探讨了ACM竞赛中数论部分的组合数和全错排概念,提供了多种求组合数的方法,包括杨辉三角性质、逆元原理以及快速幂运算。同时,分享了具体的C++代码实现,适用于解决大部分要求模运算的题目。

635

被折叠的 条评论
为什么被折叠?



