小Q和牛妹参加一个剪刀石头布的游戏,游戏用卡片来玩,每张卡片是剪刀,石头,布中的一种,每种类型的卡片有无限个。
牛妹从中选了n张卡片排成一排,正面朝下,小Q也会选择n张卡片排成一排,然后小Q和牛妹的卡片会依次进行比对,第一张对第一张,第二张对第二张…
如果小Q赢,小Q会得到一分,现在已知牛妹的每一张牌以及小Q最终的得分 s,请问小Q有多少种选择卡片的方案(多少不同的排列)
输入格式
第一行包含两个整数 n 和 s。
第二行包含 n 个整数,表示牛妹的每张卡片,每个数在[0,2]之间,0代表石头,1代表布,2代表剪刀。
输出格式
输出一个整数,表示总方案数对109+7109+7取模后的值。
数据范围
1≤n≤20001≤n≤2000,
0≤s≤20000≤s≤2000
输入样例:
3 2
0 1 2
输出样例:
6
思路:很容易能推出公式C(n,s)*2^(n-s)。C(n,s)解法很多:
1、C(n,s)=C(n-1,s)+C(n-1,s-1),复杂度比较高,大概率超时。
2、C(n,s)=A(n,s)/s!,可行,但是求得过程中,(A(n,s)/s!)mod p,会造成精度损失,s!会超出范围,但是并不能对其也每次取模。这就要用到逆元的知识了。
逆元的概念,类似于倒数的性质。
方程ax≡1(mod p),的解称为a关于模p的逆,当gcd(a,p)==1(即a,p互质)时,方程有唯一解,否则无解。对于一些题目会要求把结果MOD一个数,通常是一个较大的质数,对于加减乘法通过同余定理可以直接拆开计算,但对于(a/b)%MOD这个式子,是不可以写成(a%MOD/b%MOD)%MOD的,但是可以写为(a*b^-1)%MOD,其中b^-1表示b的逆元。
计算逆元有很多方法,这里以费马小定理举例:
费马小定理(Fermat's little theorem)是数论中的一个重要定理,在1636年提出,其内容为: 假如p是指数,且gcd(a,p)=1,那么 a^(p-1)(mod p)≡1,即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。
意思很明了,由上方的公式很容易推导出:a*a(p-2)≡1(mod p)对于整数a,p,a关于p的逆元就是a^(p-2),直接快速幂解之即可,但注意这个定理要求a,p互质!
所以,要求C(n,s) % p = A(n,s)%p * (s!^p-2),代码如下:
#include
using namespace std;
#define ll long long
const int mod = 1e9+7;
ll quick_pow(ll n, ll k)
{
ll res = 1;
while(k)
{
if(k & 1) res = res * n % mod;
n = n * n % mod;
k >>= 1;
}
return res % mod;
}
int main()
{
int n, s, t;
cin >> n >> s;
for(int i = 0; i < n ;i++) cin >> t;
if(s > n) cout << 0 << endl;
else
{
ll ans1 = 1, ans2 = 1, ans3 = 1;
for(int i = n, j = 1; j <= s; i--, j++) ans1 = ans1*i%mod;
for(int i = 1; i <= s; i++) ans2 = ans2*i%mod;
ans2 = quick_pow(ans2, mod-2);
ans3 = quick_pow(2, n-s);
cout << (ans1*ans2%mod*ans3%mod) << endl;
}
return 0;
}