组合数
P885 求组合数 I
类似动态规划
C
a
b
=
C
a
−
1
b
−
1
+
C
a
−
1
b
C_a^b=C_{a-1}^{b-1}+C_{a-1}^b
Cab=Ca−1b−1+Ca−1b
从a个苹果选b个苹果的方案数可以递推出来
数据范围:
1≤n≤10000 询问次数
1≤b≤a≤2000
MOD=
1
0
9
10^9
109+7
如果暴力,时间复杂度为O(10000*2000)=O(
2
⋅
1
0
7
2\cdot 10^7
2⋅107)
观察发现构成a,b的组合最多
200
0
2
2000^2
20002种(a2000种,b2000种),于是我们可以在O(
200
0
2
2000^2
20002)预先处理出来
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2010,mod=1e9+7;
int c[N][N];
int n;
void init(){
for(int i=0;i<N;i++){
for(int j=0;j<=i;j++){
if(!j) c[i][j]=1;//从i个苹果里选0个
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
}
int main(){
scanf("%d",&n);
init();
while(n--){
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",c[a][b]);
}
return 0;
}
P886 求组合数 II
数据范围:
1≤n≤10000 询问次数
1≤b≤a≤
1
0
5
10^5
105
MOD=
1
0
9
10^9
109+7
C
a
b
=
a
!
(
a
−
b
)
!
⋅
b
!
\bm C_a^b=\frac{a!}{(a-b)!\cdot b!}
Cab=(a−b)!⋅b!a!
因为有除法,涉及取余时要求逆元转为乘法,fact[i]记录i的阶乘,infact[i]记录i的阶乘mod MOD的逆元
所以表达式变为
C
a
b
C_a^b
Cab=fact[a]
⋅
\cdot
⋅infact[a-b]
⋅
\cdot
⋅infact[b]
因为a,b都与MOD互质,所以存在逆元(快速幂)
时间复杂度O(a
⋅
\cdot
⋅log MOD)
#include<iostream>
using namespace std;
typedef long long LL;
int n;
const int N=1e5+10,mod=1e9+7;
int fact[N],infact[N];
int qmi(int a,int k,int p){
int res=1;
while(k){
if(k&1) res=(LL)res*a%mod;
a=(LL)a*a%mod;
k>>=1;
}
return res;
}
int main(){
fact[0]=infact[0]=1;
for(int i=1;i<N;i++){
fact[i]=(LL)fact[i-1]*i%mod; //本行时间复杂度为O(1)
infact[i]=(LL)infact[i-1]*qmi(i,mod-2,mod)%mod;//本行时间复杂度为O(logMOD)
} //所以整个预处理时间复杂度为O(a*(1+logMOD))=O(a*logMOD);
scanf("%d",&n);
while(n--){
int a,b;
scanf("%d%d",&a,&b);
int t=(LL)fact[a]%mod*infact[a-b]%mod*infact[b]%mod;
printf("%d\n",t);
}
return 0;
}
P887 求组合数 III
数据范围:
1≤n≤20
1≤b≤a≤
1
0
18
10^{18}
1018
1≤MOD≤
1
0
5
10^5
105 题目保证MOD为质数
此时MOD比a和b小,不能保证逆元一定存在了(有可能是MOD的倍数),于是引入了lucas定理:
C
a
b
≡
C
a
m
o
d
p
b
m
o
d
p
⋅
C
a
/
p
b
/
p
(
m
o
d
p
)
\bm C_a^b\equiv C_{amod p}^{b mod p}\cdot C_{a/p}^{b/p}(\bmod p)
Cab≡Camodpbmodp⋅Ca/pb/p(modp)
时间复杂度:O(
l
o
g
p
a
⋅
p
⋅
l
o
g
p
log_p^a\cdot p\cdot log p
logpa⋅p⋅logp)
分析:
#include<iostream>
using namespace std;
typedef long long LL;
int qmi(int a,int k,int p){
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,int p){
if(b>a) return 0;
int res=1;
//根据定义求Cab
for(int i=1,j=a;i<=b;i++,j--){
res=(LL)res*j%p; //计算分子a*(a-1)*(a-2)···*(a-(b-1))
res=(LL)res*qmi(i,p-2,p)%p;//计算分母,但要用逆元
}
return res;
}
int lucas(LL a,LL b,int p){
if(a<p&&b<p) return C(a,b,p);
return (LL)C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
//a%p和b%p一定比p小,但a/p和b/p不一定
}
int main(){
int n;
scanf("%d",&n);
while(n--){
LL a,b;
int p;
scanf("%lld%lld%d",&a,&b,&p);
printf("%d\n",lucas(a,b,p));
}
return 0;
}
P888 求组合数 IV
高精度
根据定义
C
a
b
=
a
!
(
a
−
b
)
!
⋅
b
!
C_a^b=\frac{a!}{(a-b)!\cdot b!}
Cab=(a−b)!⋅b!a!,所以我们把
C
a
b
C_a^b
Cab分解质因数,方法为先分解
a
!
a!
a!,
(
a
−
b
)
!
(a-b)!
(a−b)!,
b
!
b!
b!,然后对于相同的质因数,用分子的次数减分母的次数,结果就是答案的次数
求a!里含质因子p的个数公式:
⌊
a
!
p
⌋
+
⌊
a
!
p
2
⌋
+
⌊
a
!
p
3
⌋
+
⌊
a
!
p
4
⌋
+
⌊
a
!
p
5
⌋
+
⋅
⋅
⋅
⋅
⋅
⋅
\lfloor \frac{a!}{p} \rfloor+\lfloor \frac{a!}{p^2} \rfloor+\lfloor \frac{a!}{p^3} \rfloor+\lfloor \frac{a!}{p^4} \rfloor+\lfloor \frac{a!}{p^5} \rfloor+······
⌊pa!⌋+⌊p2a!⌋+⌊p3a!⌋+⌊p4a!⌋+⌊p5a!⌋+⋅⋅⋅⋅⋅⋅
时间复杂度:O(log p)
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5010;
int primes[N],cnt;
bool st[N];
int sum[N];
void get_primes(int n){ //线性筛
for(int i=2;i<=n;i++){
if(!st[i]) primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++){
st[primes[j]*i]=true;
if(i%primes[j]==0) break;
}
}
}
vector<int> mul(vector<int> a,int b){
vector<int> c;
int t=0;
for(int i=0;i<a.size();i++){
t+=a[i]*b;
c.push_back(t%10);
t/=10;
}
//用循环是因为每一位a都会乘以一个整数b,所以最后结果可能出现多位
while(t){
c.push_back(t%10);
t/=10;
}
//不用处理前导零,因为这题不可能出现乘0
return c;
}
int get(int n,int p){ //求n!中包含p的次数
int res=0;
while(n){
res+=n/p;
n/=p;
}
return res;
}
int main(){
int a,b;
scanf("%d%d",&a,&b);
get_primes(a);
for(int i=0;i<cnt;i++){
int p=primes[i];
//把分子和分母相约,剩下的就是最终答案
sum[i]=get(a,p)-get(b,p)-get(a-b,p);//记录primes[i]一共要乘多少次
}
vector<int> res;
res.push_back(1);
for(int i=0;i<cnt;i++){
for(int j=0;j<sum[i];j++){
res=mul(res,primes[i]);
}
}
for(int i=res.size()-1;i>=0;i--){
printf("%d",res[i]);
}
return 0;
}