C.打牌的贝贝
题意:
A和B在玩一个卡牌游戏,共有 2n 张卡牌,每张牌上都有一个整数,介于1和2n之间,所有牌上的数字都是不同的。发牌阶段二人都拿到了 n 张牌。每个回合,游戏的过程如下:
1.若此时A没有牌了,则B判负。
2,A打出一张牌。
3.然后B需要打出一张牌,使得其上的数字比A最新打出的一张牌上的数字大,如果此时B无法打出这样的牌,则B判负。
问:在A和B都足够聪明的前提下,求A和B各自获胜的情况数量。有T组测试样例,答案对 取模。
数据范围:
1 ≤ T ≤
1 ≤ n ≤
思路:
官方题解非常清晰,该题后者获胜的情况数就是括号匹配模型中合法的括号序列数,即卡特兰数。所以先手获胜的方案数为 - Catalan n;后手获胜的方案数为Catalan n。
这里主要是想大致整理下求卡特兰数时的不同求法和细节处理——
卡特兰数公式——
(1)
(2)
(3)
如果用公式1,需要求解 (n+1) 关于 p 的逆元,那么如何求 a 关于 p 的逆元(关键!!!只有 a 与 p 互质时,a 才有关于 p 的逆元。)——
法一(费马小定理):
方法:
证明:根据费马小定理:当p是质数时, ≡ 1(mod p) -->
≡ 1/a (mod p) -->
≡ inv(a) (mod p) --> a关于p的逆元为
实现:快速幂
//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是质数时):
方法:
证明:设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一定是质奇数,所以当一定是整数,可以得出,当x =
时,成立。所以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[]求单个数的逆元):
方法:
证明: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;
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:该题是卡特兰数中的括号匹配模型,关键就是根据题目能看出来是什么模型后,再套板子就明了多了。这里看不同的代码,大致整理了下求逆元的方法,其实究其根本是一个道理,不过看不同的求法还是挺能加深理解的 (๑•̀ㅂ•́)و✧。