2019腾讯实习提前批笔试题-猜拳游戏(逆元,快速幂)

小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;
}

 

你可能感兴趣的:(快速幂取模,数论)