牛客练习赛105 C.打牌的贝贝(卡特兰数+逆元)

C.打牌的贝贝

题意:

A和B在玩一个卡牌游戏,共有 2n 张卡牌,每张牌上都有一个整数,介于1和2n之间,所有牌上的数字都是不同的。发牌阶段二人都拿到了 n 张牌。每个回合,游戏的过程如下:
1.若此时A没有牌了,则B判负。
2,A打出一张牌。
3.然后B需要打出一张牌,使得其上的数字比A最新打出的一张牌上的数字大,如果此时B无法打出这样的牌,则B判负。

问:在A和B都足够聪明的前提下,求A和B各自获胜的情况数量。有T组测试样例,答案对10^{9}+7 取模。

数据范围:

1 ≤ T ≤ 10^{6}

1 ≤ n ≤ 10^{6}

思路:

官方题解

官方题解非常清晰,该题后者获胜的情况数就是括号匹配模型中合法的括号序列数,即卡特兰数。所以先手获胜的方案数为 C_{2n}^{n} - Catalan n;后手获胜的方案数为Catalan n。

这里主要是想大致整理下求卡特兰数时的不同求法和细节处理——
卡特兰数公式——
(1)f[n] =\frac{ f[n-1] *(4n-2)}{n+1}
(2)f[n] = \frac{C_{2n}^{n}}{C_{2n}^{n-1}}
(3)f[n] = \frac{C_{2n}^{n}}{n+1}

 如果用公式1,需要求解 (n+1) 关于 p 的逆元,那么如何求 a 关于 p 的逆元(关键!!!只有 a 与 p 互质时,a 才有关于 p 的逆元。)——

法一(费马小定理)
方法:inv[a] = a^{p-2}
证明:根据费马小定理:当p是质数时,a^{p-1} ≡ 1(mod p) --> a^{p-2} ≡ 1/a (mod p) --> a^{p-2} ≡ inv(a) (mod p) --> a关于p的逆元为 a^{p-2}
实现:快速幂

//p为质数时快速幂求逆元
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;
}
for (int i = 1; i <= N; i++)
{
    inv[i] = qmi(i,p-2,p);
}

法二(扩展欧几里得定理)
方法:扩展欧几里得定理:若 gcd(a,b) = d,那么一定存在x,y使得 ax+by = d.
求逆元:ax+by=1,如果ab互质,有解,这个解x即a关于b的逆元,y即b关于a的逆元。
证明:两边同时求余b:
          a*x%b+b*y%b = 1%b
          a*x%b = 1%b
          a*x = 1 (mod b)
实现:我们利用欧几里得算法求出x,y和d后判断exgcd(a,b,x,y)的返回值 d 是否为1,为1说明 gcd(a,b) = 1,即求得的x就是a关于b的逆元

int exgcd(int a, int b,int &x,int &y)
{
	if (!b)
	{
		x = 1, y = 0;    //gcd(a,0)=a,所以a*1+b*0=a -> x=1,y=0
		return a;
	}
	int d = exgcd(b, a % b, y, x);     //得到gcd(a,b)
	y -= (a / b) * x;

	另一种较直观但不简洁的写法
	//int d = exgcd(b, a % b, y, x);     //递归计算
	//int temp = x;                      //记录递归结束后由某层的(b,a%b)得到的x
	//x = y;                             //由递归得到的y倒推出该层函数的x(x1=y2)
	//y = temp - (a / b) * y;            //由递归得到的x倒推出该层函数的y(y1=x2-(a/b)*y2)

	return d;
}

int main()
{
    int a, b, x, y;
    cin >> a >> b;                                                    // 求a关于模b的逆元
    cout << (exgcd(a, b, x, y) == 1 ? (x % b + b) % b : -1) << endl;  // -1表示逆元不存在
 
    return 0;
}

法三(当p是质数时)
方法:inv[a] = (p-\frac{p}{a}) * inv[p%a]%p
证明:设x = p%a,y = p/a
           所以x+y*a = p
           (x+y*a)%p = 0
          移项得x%p = (-y)%p
          inv[a] = (p-y)*inv[x]%p
          于是 inv[a] = (p-p/a)*inv[p%a]%p
实现:然后又一直递归到1为止,因为1的逆元就是1
这个方法不限于求单个逆元,可以在O(n)的复杂度内算出n个数的逆元

注意:inv[1] = 1,因为(1*1)%mod = 1
注意求2的逆元inv[2]时比较特殊,它可以直接考虑 x 取何值时(x一定为整数),(2*x)%mod=1。
因为mod一定是质奇数,所以当\frac{mod+1}{2}一定是整数,可以得出,当x = \frac{mod+1}{2}时,成立。所以2的逆元为\frac{mod+1}{2}。  

之后的3,4,5……不能再直接推了,因为无法确定(mod+1)/i是否为整数。直接用公式求即可。

inv[1]= 1;
for (int i = 2; i <= N; i++)
    inv[i] = (mod - mod / i) * inv[mod % i] % mod;

法四(通过求得的阶乘fact[]和阶乘的逆元infact[]求单个数的逆元):
方法:inv[a] = fact[a -1]*infact[a]%p
证明:fact[a-1] ≡ 1*2*3*……*(a-1) (mod p)
          infact[a]  ≡ 1/1*2*3*……*a (mod p)
          inv[a] ≡ 1/a (mod p) = (1/1*2*3*……*(a-1) * a) *(1*2*3*……*(a-1)) (mod p) 
          所以 inv[a] = fact[a-1] * infact[a] % p

fact[0] = infact[0] = 1;        //0的阶乘和逆元都为1
//预处理阶乘和阶乘的逆元预处理
for (int i = 1; i < N; i++)
{
    fact[i] = (LL)fact[i - 1] * i % mod;                          //求阶乘
    infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;   //求阶乘的逆元
}
for(int i = 1; i < N; i++)
{
     inv[i] = fact[i-1] * infact[i] % mod;
}  

PS:在整理时看到许多求阶乘的逆元的形式,也整理下求阶乘的逆元——

法一:用费马小定理求每一个 i 的逆元,然后通过循环累乘将每个 1 ~ i 的逆元相乘,求出 i 的阶乘的逆元

     infact[0] = 1;
     for (int i = 1; i <= N; i++)
    {
        infact[i] = infact[i - 1] * qmi(i, mod - 2) % mod;
    }


法二:直接用循环求得的 i 的阶乘 fact[i],使用费马小定理求得 i 的阶乘的逆元

     for (int i = 1; i <= N; i++)
    {
        infact[i] = qmi(fact[i], mod - 2) % mod;
    }


法三:先求出N的阶乘的逆元,再逆序循环求I的阶乘的逆元时,通过(i+1)阶乘的逆元乘以(i+1)求得因为 inv[(i+1)!] = inv[i!] * inv[i+1] = inv[i!] / (i+1) (mod p) = inv[(i+1)!] * (i+1)

    infact[N] = qmi(fact[N], mod - 2) % mod;
    for(int i = N-1; i >=0; i--)
    {
         infact[i] = infact[i+1]*(i+1)%mod;
    }

Code1:
 

//使用公式f[n] = C(2n,n)/(n+1)求卡特兰数
#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 2e6 + 10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9+7;

typedef pair<int, int>PII;

int n, m;
int a[N], fact[N],infact[N];

//快速幂
int qmi(int x, int k)
{
    int res = 1;
    while (k)
    {
        if (k & 1)res = res * x % mod;
        x = x * x % mod;
        k >>= 1;
    }
    return res;
}

//预处理阶乘fact[]和阶乘的逆元infact[]
void init()
{
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; i++)
    {
        fact[i] = fact[i - 1] * i % mod;
        //infact[i] = infact[i - 1] * qmi(i, mod - 2) % mod;
        infact[i] = qmi(fact[i], mod - 2) % mod;
    }
}

//求C(a,b)
int C(int a, int b)
{
    if(a < b) return 0;
    return fact[a] * infact[a - b] % mod * infact[b] % mod;
}

void solve()
{
    cin >> n;
    cout << C(2 * n, n - 1) % mod << " " << (C(2 * n, n) - C(2 * n, n - 1) + mod) % mod << endl;
}

signed main()
{
    IOS;
    int t;
    //int t = 1;
    cin >> t;
    init();
    while (t--)
    {
        solve();
    }
    return 0;
}

Code2:

//利用公式f[n] = f[n-1]*(4n-2)/(n+1)求卡特兰数
#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 2e6 + 10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9 + 7;

typedef pair<int, int>PII;

int n, m;
int a[N], fact[N], infact[N], inv[N], f[N];

//快速幂
int qmi(int x, int k)
{
    int res = 1;
    while (k)
    {
        if (k & 1)res = res * x % mod;
        x = x * x % mod;
        k >>= 1;
    }
    return res;
}

//预处理阶乘fact[]和阶乘的逆元infact[]
void init()
{
    //预处理1~N阶乘
    fact[0] = 1;
    for (int i = 1; i <= N; i++)
        fact[i] = fact[i - 1] * i % mod;
    //预处理1~N阶乘的逆元
    infact[N] = qmi(fact[N], mod - 2) % mod;
    for(int i = N-1; i >=0; i--)
        infact[i] = infact[i+1]*(i+1)%mod;
    //预处理1~N倒数的逆元
    inv[1]= 1;
    for (int i = 2; i <= N; i++)
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    //求卡特兰数
    f[0] = 1;
    for (int i = 1; i <= N; i++)
        f[i] = (4 * i - 2) * inv[i + 1] % mod * f[i - 1] % mod;
}

//求C(a,b)
int C(int a, int b)
{
    if (a < b) return 0;
    return fact[a] * infact[a - b] % mod * infact[b] % mod;
}

void solve()
{
    cin >> n;
    int y = f[n];
    int x = ((C(2*n, n) - y) % mod + mod) % mod;
    cout << x << " " << y << endl;
}

signed main()
{
    IOS;
    int t;
    //int t = 1;
    cin >> t;
    init();
    while (t--)
    {
        solve();
    }
    return 0;
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:该题是卡特兰数中的括号匹配模型,关键就是根据题目能看出来是什么模型后,再套板子就明了多了。这里看不同的代码,大致整理了下求逆元的方法,其实究其根本是一个道理,不过看不同的求法还是挺能加深理解的  (๑•̀ㅂ•́)و✧。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值