組合數問題一:
給定n組詢問,每組詢問給定兩個整數a,b,請你輸出
的值。
1≤n≤10000,
1≤b≤a≤2000
模板題鏈接:組合數一
問題特點:數據組數較多,a,b的范圍較小,且要求對一個定值取模。
解決方法:楊輝三角
因為a,b范圍非常小,直接利用楊輝三角打表即可。
代碼實現:
#include <cstdio> #include <algorithm> #include <iostream> using namespace std; const int N = 2010, mod = 1e9+7; int s[N][N]; void start() { s[0][0]=1; for(int i=1;i<=N-5;i++) for(int j=1;j<=i;j++) s[i][j]=(s[i-1][j]+s[i-1][j-1])%mod; } int main() { start(); int n;scanf("%d",&n); for(int i=1;i<=n;i++) { int a,b; scanf("%d%d",&a,&b); printf("%d\n",s[a+1][b+1]); } return 0; }
組合數問題二
給定n組詢問,每組詢問給定兩個整數a,b,請你輸出
的值。
1≤n≤10000,
1≤b≤a≤105
模板題鏈接:組合數二
問題特點:數據組數較多,a,b范圍較大,且要求對一個定值取模。
解決方法:乘法逆元
利用乘法逆元將a/b mod p轉化成a*b-1 mod p。
然后將階乘與階乘的逆元分別打表即可。
遞推式:
① n! = (n-1)! * n
② n!-1 = (n-1)!-1 * np-2
②的證明:
(n-1)!-1 = (n-1)!p-2
(n-1)!p-2 * np-2 = n!p-2
代碼實現:
#include <iostream> #include <cstdio> using namespace std; typedef long long ll; const int N = 100000+10, mod = 1e9 + 7; int jc[N],ny[N]; int qmi(int a,int k,int p) { int res=1%p; while(k) { if(k&1)res=(ll)res*a%p; a=(ll)a*a%p; k>>=1; } return res; } void start() { jc[0]=1;ny[0]=1; for(int i=1;i<=N-5;i++) { jc[i]=(ll)jc[i-1]*i%mod; ny[i]=(ll)ny[i-1]*qmi(i,mod-2,mod)%mod; } } int main() { start(); int n;scanf("%d",&n); for(int i=1;i<=n;i++) { int a,b;scanf("%d%d",&a,&b); int ans=(ll)jc[a]*ny[b]%mod*ny[a-b]%mod; printf("%d\n",ans%mod); } return 0; }
組合數問題三
給定n組詢問,每組詢問給定三個整數a,b,p,其中p是質數,請你輸出
的值
1≤n≤20,
1≤b≤a≤1018,
1≤p≤105
模板題鏈接:組合數三
問題特點:數據組數較少,a,b范圍很大,p的值非定值。
解決方法:Lucas定理
若p是質數,則對於任意整數1≤m≤n,有:
相當於是把n和m表示成p進制數,對p進制下的每一位分別計算組合數,最后再乘起來。
利用Lucas定理將目標組合數遞歸分解,直到m和n小於p為止。
然后對於每個分解出來的m和n的范圍均在p以內的組合數,就可以放心地根據組合數的定義直接求解。
當然,對於每一步計算過程,都需要對p取模,遇到除法的利用快速冪轉化成乘法逆元即可。
代碼實現:
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> using namespace std; typedef long long ll; int p; //用的次數比較多的變量,一般設置的變量名比較短,且一般設成全局變量 int qmi(int a,int k) { int res=1; while(k) { if(k&1) { res=(ll)res * a % p; } a=(ll)a * a % p; k>>=1; } return res; } int C(int a,int b) { if(a<b)return 0; int res=1; for(int i=a,j=1;j<=b;i--,j++) { res = (ll)res * i % p; res = (ll)res * qmi(j,p-2)%p; } return res; } int lucas(ll a,ll b) { if(a<p&&b<p)return C(a,b); { return (ll)C(a%p,b%p)*lucas(a/p,b/p)%p; } } int main() { int n;scanf("%d",&n); while(n--) { ll a,b; scanf("%lld%lld%d",&a,&b,&p); //scanf是c的語法,cin是c++的語法,函數內部自帶& printf("%d\n",lucas(a,b)); } return 0; }
組合數問題四
輸入a,b,求
的值。
注意結果可能很大,需要使用高精度計算。
1≤b≤a≤5000
模板題鏈接:組合數四
問題特點:只有一組數據,a,b取值不大,但沒有要求取模。
解決方法:高精度
題目沒有要求取模,就沒辦法在過程中保證數據的大小,只能暴力高精度。
根據組合數的定義,可得:
在循環過程中一邊乘一邊除即可,這樣便只需要寫兩個函數:高精度乘低精度,高精度除以低精度。
代碼實現:
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> using namespace std; struct largeint{ int len; int num[10010]; }; largeint lar_mult(largeint a,int b) { for(int i=1;i<=a.len;i++)a.num[i]*=b; for(int i=1;i<=a.len;i++) { a.num[i+1]+=a.num[i]/10; a.num[i]%=10; } if(a.num[a.len+1]>0)a.len++; while(a.num[a.len]>9) { a.len++; a.num[a.len]+=a.num[a.len-1]/10; a.num[a.len-1]%=10; } while(a.num[a.len]==0&&a.len>1)a.len--; return a; } largeint lar_div(largeint a,int b) { for(int i=a.len;i>=1;i--) { if(i>1)a.num[i-1]+=(a.num[i]%b)*10; a.num[i]/=b; } while(a.num[a.len]==0&&a.len>1)a.len--; return a; } void print(largeint n) { for(int i=n.len;i>=1;i--) printf("%d",n.num[i]); printf("\n"); return; } int main() { int a,b; scanf("%d%d",&a,&b); largeint res; res.num[1]=1;res.len=1; for(int i=a,j=1;j<=b;i--,j++) { res=lar_mult(res,i); res=lar_div(res,j); } print(res); return 0; }