[AcWing 569-猜拳游戏] 快速求组合数的方法
题目链接:AcWing 569-猜拳游戏
小Q和牛妹参加一个剪刀石头布的游戏,游戏用卡片来玩,每张卡片是剪刀,石头,布中的一种,每种类型的卡片有无限个。
牛妹从中选了n张卡片排成一排,正面朝下,小Q也会选择n张卡片排成一排,然后小Q和牛妹的卡片会依次进行比对,第一张对第一张,第二张对第二张…
如果小Q赢,小Q会得到一分,现在已知牛妹的每一张牌以及小Q最终的得分 s,请问小Q有多少种选择卡片的方案(多少不同的排列)
输入格式
第一行包含两个整数 n 和 s。
第二行包含 n 个整数,表示牛妹的每张卡片,每个数在[0,2]之间,0代表石头,1代表布,2代表剪刀。
输出格式
输出一个整数,表示总方案数对109+7
取模后的值。
数据范围
1≤n≤2000
,
0≤s≤2000
输入样例:
3 2
0 1 2
输出样例:
6
这个题有个小坑,就是第二行的输入没有什么用,就是来干扰你的;分析清楚后就是一个求组合数的问题
思路简单:
1.先在n中选s个,作为必赢的方案,即Cns;
2.剩下的n-s有两种选择,即2n-s
3.总的方案就是Cns 2n-s*
但是如果直接用公式Cns=Cn-1s+Cn-1s-1或者带n!/(s!*(n-s)!)都会爆,或者太费时
所以这里重要的如何去求Cns;
大概思路就是:
1.先把n里面所有的素数找出来(因为下面要进行质因数分解)
2.对于每个数都可以写成以素数为底,任意次方的乘积形式.如12=2231
3.所以我们可以先对分子n!和分母s!(n-s)!进行约分,因为都有可能出现相同的底数
4.所以我们就去找同一个底数,分子和分母分别出现的次数,然后用分子-分母(即约分操作),这里有个简单的技巧去求N!中某个数p出现的次数cnt=(N/p)+(N/p2)+(N/p3)+(N/p4)+…+;
5.计算出所有底数(出现的次数)的乘机,然后再计算出2n-s,然后两者相乘即可
代码
#include<iostream>
using namespace std;
const int N=1e5+5,mod=1e9+7;
int primes[N],cnt, m,n,a;
bool vis[N];
//获取0~N所有的素数
void get_primes(int N) {
for(int i=2; i<=N; i++) {
if(!vis[i]) {
primes[cnt++]=i;
for(int j=i+i; j<=N; j+=i) {
vis[j]=true;
}
}
}
}
//返回n的阶乘里面包含几个p
int get_numberOF(int n,int p) {
int res=0;
while(n) {
res+=n/p;
n/=p;
}
return res;
}
//计算a的b次幂,结果对mod取余
long long fastPower(int a,int b,int mod) {
long long res=1;
while(b) {
if(b&1) {
res=res*a%mod;
}
b>>=1;
a=(long long)a*a%mod;
}
return res;
}
int main() {
cin>>m>>n;
get_primes(m);
int power,ans=fastPower(2,m-n,mod);//先计算2的m-n次方
for(int i=0; i<cnt; i++) {
power=get_numberOF(m,primes[i])-get_numberOF(n,primes[i])-get_numberOF(m-n,primes[i]);//计算出剩下的次方数
ans= ans*fastPower(primes[i],power,mod) %mod;
}
cout<<ans<<endl;
return 0;
}