其實通常都只是求一個組合數:算法
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<m) 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時!spa
//特殊定義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; } }
###二項式反演code
能夠表示成排序
$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便可。it
標準模板,盧卡斯定理默認裝載從新求組合數。編譯
<details> ```cpp 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<m) 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<m) 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(),必要時修改常量
</details> --- 精簡模板,須要保證固定MOD爲質數。 <details> ```cpp 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; } //3. 乘法逆元 快速冪+費馬小定理,要求p必須是質數 (依賴1. 快速冪) inline ll inv_p(ll n,ll p=MOD) { return qpow(n,p-2,p); } //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; } //10. 利用階乘和階乘逆元計算組合數C_n^m %mod (依賴7. 線性求階乘,階乘乘法逆元) inline ll C(ll n,ll m,ll mod=MOD) { return fac[n]*invfac[n-m]%mod*invfac[m]%mod; } }; using namespace combinatorics; //注意須要init(),必要時修改常量
</details>
擴展盧卡斯定理要在別的隨筆中找。