信竞-排列组合
排列组合
目录
一、求组合数
关于排列组合:math
一、求组合数
对于相同的问题:
给定
n 组询问,每组询问给定两个整数a ,b ,请你输出C_a^b\mod(10^9+7) 的值。
现进行难度递增的解决方案.
Frist time
1\leq n\leq 10000,1\leq b \leq a \leq 2000
分析一下:假如暴力做,即利用
然而我们知道,这道题的核心是组合数,而组合数有着如下性质:
C_n^m=C_{n-1}^m+C_{n-1}^{m-1}(n>1)
由此可以想到把所有
我们用一个c[N][N]的二维数组来表示组合数,即得c[i][j]=c[i-1][j-1]+c[i-1][j],而我们的推导过程如下:
……
其实,也就是递推。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2010,mod=1e9+7;
int c[N][N];
void init(){
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
if(!j) c[i][j]=1;
else c[i][j]=(ll)(c[i-1][j]+c[i-1][j-1])%mod;
}
int main(){
init();
int n;
scanf("%d",&n);
while(n--){
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",c[a][b]);
}
return 0;
}
时间复杂度
Second time
1\leq n\leq 10000,1\leq b \leq a \leq 100000
此时发现,如果再用递推,则
对于组合数来讲,还有一种性质:
C_n^m=\frac{n!}{m!\cdot (n-m)!}
那么不如来预处理
\frac{a}{b}\mod p \neq \frac{a \mod p}{b \mod p}
所以我们需要考虑模的逆元,解释略 。
具体地讲,我们以fac[N],infac[N]表示数的阶乘和数阶乘的逆元,此时
b!=(b-1)!\cdot b, (b!)^{-1}=[(b-1)!\cdot b]^{-1}=[(b-1)!]^{-1}\cdot b^{-1}
即fac[i]=fac[i-1]*i%mod; infac[i]=infac[i-1]*qmi(i,mod-2,mod)%p;
此时问题全部解决,实践开始。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10,mod=1e9+7;
int fac[N],infac[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 init(){
fac[0]=infac[0]=1;
for(int i=1;i<N;i++){
fac[i]=(ll)fac[i-1]*i%mod;
infac[i]=(ll)infac[i-1]*qmi(i,mod-2,mod)%mod;
}
}
int main(){
init();
int n;
scanf("%d",&n);
while(n--){
int a,b;
scanf("%d%d",&a,&b);
printf("%d",(ll)fac[a]*infac[b]%mod*infac[a-b]%mod);
}
return 0;
}
时间复杂度
Third time
给定
n 组询问,每组询问给定三个整数a ,b ,p ,请你输出C_a^b\mod p 的值。1\leq n\leq 20,1\leq b \leq a \leq 10^{18},1\leq p\leq 10^5
Lucas定理:
(不要碰这玩意儿!!!)
代码:
#include<iostream>
#include<algorithm>
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)//自变量类型int
{
if(b>a)return 0;//漏了边界条件
int res = 1;
// a!/(b!(a-b)!) = (a-b+1)*...*a / b! 分子有b项
for(int i=1,j=a;i<=b;i++,j--)//i<=b而不是<
{
res = (LL)res*j%p;
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;
}
int main()
{
int n;
cin >> n;
while(n--)
{
LL a,b;
int p;
cin >> a >> b >> p;
cout << lucas(a+b,a,p) << endl;
}
return 0;
}
Fourth time
询问给定两个整数
n ,m ,请你输出C_n^m 的值。
众所周知,高精度可以在不顾及时间的情况下解决任何计算量爆炸的问题。
从定义出发,
我们可以先把
- 如何分解质因数?
利用
- 如何对
n! 分解质因数?
对于素数
则对于每一个素数
理论可行,实践开始。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=5010;
int primes[N],cnt;
int a[N];
bool st[N];
void get_primes(int x){
for(int i=2;i<=x;i++){
if(!st[i]) primes[cnt++]=i;
for(int j=0;primes[j]<=x/i;j++){
st[primes[j]*i]=true;
if(i%primes[j]==0) break;
}
}
}
int get(int n,int p){
int res=0;
while(n){
res+=n/p;
n/=p;
}
return res;
}
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;
}
while(t){
c.push_back(t%10);
t/=10;
}
return c;
}
int main(){
int n,m;
cin>>n>>m;
get_primes(n);
for(int i=0;i<cnt;i++){
int p=primes[i];
a[i]=get(n,p)-get(m,p)-get(n-m,p);
}
vector<int> res;
res.push_back(1);
for(int i=0;i<cnt;i++)
for(int j=1;j<=a[i];j++)
res=mul(res,primes[i]);
for(int i=res.size()-1;i>=0;i--)
printf("%d",res[i]);
puts("");
return 0;
}