总结:当前所见到的应用卡特兰数的题型大致都是这么一种。
给你两个操作A,B, A,B都会进行N次。总共2 *N次后结束,问在所有前缀中,A操作次数 多于当然也可以等于 B操作次数 的情况的总数。
如:总共进行4次
ABAB
AABB
这种的题目的时候就可以使用卡特兰数的思路:
1.首先将这两个操作分别对应于X和Y轴,其中X轴对应A,Y轴对应B。
那么A进行一次,点就右移一个,B进行一次,Y就上移一个。
而A,B次数相同,都为N所以序列的最后都会到(N,N)点。
所以满足 y = x 即以下(包括这个条线上)的点都符合条件,
而在这条线上的点都不符合条件。也就是不符合条件的点必然都经过了线y = x + 1.
所以我们将(N,N)关于 y = x + 1对称,那个对称点的坐标就是(N - 1,N + 1).
我们从(0,0)到达(N,N)点的方式共 c[2n][n]
从(0,0)到(N - 1, N + 1)的方式共 c[2n][n - 1]
所以 c[2n][n] - c[2n][n - 1]就是符合条件的情况
2n ! / ( n ! * n!) - 2n ! / ( (n - 1)! * (n + 1)! )
--->2n ! * (n + 1) / ( (n + 1)! * n! ) - 2n ! * n / ( (n ! * (n + 1) ! )
--->2n ! / ( (n + 1)! * n ! )
--->c[2n][n] / ( n + 1) (这就是最后的结论)
题目链接:https://www.acwing.com/problem/content/891/
题目:
给定 n 个 0 和 n 个 1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0的个数都不少于 1 的个数的序列有多少个。
输出的答案对 1e9+7 取模。
输入格式
共一行,包含整数 n。
输出格式
共一行,包含一个整数,表示答案。
数据范围
1≤n≤1e5
输入样例:
3
输出样例:
5
分析:
0与1最后的总次数都相同,并且前缀中0的次数总是大于等于1的次数,所以为卡特兰数,用公式。 并且数据范围为1e5, 2 * a 也就是 2e5, 直接用组合数的方式1即可。
代码:
# include <iostream>
using namespace std;
const int mod = 1e9 + 7;
int qmi(int a ,int b , int mod)
{
int res = 1;
while(b)
{
if(b & 1)
{
res = (long long)res * a % mod;
}
b >>= 1;
a = (long long)a * a % mod;
}
return res;
}
int main()
{
int n;
scanf("%d",&n);
int a = 2 * n, b = n;
int res = 1;
for(int j = 1 , i = a ; j <= b ; j++ , i--)
{
res = (long long)res * i % mod * qmi(j,mod - 2 , mod) % mod;
}
res = (long long)res * qmi(n + 1,mod - 2 , mod) % mod;
printf("%d\n",res);
return 0;
}
题目链接: https://www.acwing.com/problem/content/132/
题目:
一列火车 nn 节车厢,依次编号为 1,2,3,…,n1,2,3,…,n。
每节车厢有两种运动方式,进栈与出栈,问 nn 节车厢出栈的可能排列方式有多少种。
输入格式
输入一个整数 nn,代表火车的车厢数。
输出格式
输出一个整数 ss 表示 nn 节车厢出栈的可能排列方式数量。
数据范围
1≤n≤600001≤n≤60000
输入样例:
3
输出样例:
5
分析:
此题将压栈看做操作A,把退栈看作操作B。那么符合实际的情况就是A的操作永远多于或者等于B的操作。并且A和B的操作数相同,所以也是卡特兰数。
同时注意
1.此题需要使用到高精度,
2.所以我们使用组合数五的方式 , 筛质数,指数,用高精度乘法即可。同时还有一个除以n - 1,那么我们还是用乘法,那就筛出 n - 1的质数和指数,从中去掉即可。
因为 2n ! / n ! / n! / (n +1 ) 为一个整数, 如果存在值,则必定会有质数和对应指数,所以2n !的质数和指数 - 2 * (n!)的质数中的指数, - (n + 1)中的质数和指数,必定不会出现质数对应的指数为-1的情况。
3.高精度乘法中需要用压位,需要压8位才不会TLE。而在高精度乘法中压位的话,int数组一般压4位,long long数组一般压8位,所以用long long。
压位的题解链接:https://mp.csdn.net/mp_blog/creation/editor/118580115
代码实现:
# include <iostream>
# include <vector>
using namespace std;
const int N = 60000 * 2 + 10;
int prim[N],cnt;
bool choose[N];
int num[N];
int n;
vector<long long> mul(vector<long long> a , int b)
{
long long t = 0;
vector<long long> c;
for(int i = 0; i < a.size() ; i++)
{
t = t + a[i] * b;
c.push_back(t % 100000000);
t /= 100000000;
}
while(t)
{
c.push_back(t % 100000000);
t /= 100000000;
}
while(c.size() > 1 && c.back() == 0)
{
c.pop_back();
}
return c;
}
int get(int x , int p) // 求x! 中 p 的总指数
{
int res = 0;
while(x)
{
res += x / p;
x /= p;
}
return res;
}
int main()
{
scanf("%d",&n);
int a = 2 * n , b = n;
for(int i = 2 ; i <= a ; i++) // 筛出所有再 2 * n 以内的质数
{
if(!choose[i])
{
prim[++cnt] = i;
}
for(int j = 1 ; prim[j] <= a / i ; j++)
{
choose[prim[j] * i] = true;
if(i % prim[j] == 0)
{
break;
}
}
}
for(int i = 1 ; i <= cnt ; i++)
{
num[i] = get(a,prim[i]) - 2 * get(b,prim[i]);
}
int c = n + 1;
for(int i = 1 ; i <= cnt && prim[i] <= c; i++)
{
if(c % prim[i] == 0)
{
int res = 0;
while(c % prim[i] == 0)
{
res++;
c /= prim[i];
}
num[i] -= res;
}
}
vector<long long> p;
p.push_back(1);
for(int i = 1 ; i <= cnt ; i++)
{
for(int j = 1 ; j <= num[i] ; j++)
{
p = mul(p,prim[i]);
}
}
/*
for(int i = p.size() - 1 ; i >= 0; i--)
{
printf("%d",p[i]);
}
*/
printf("%lld",p.back());
for(int i = p.size() - 2 ; i >= 0 ; i--)
{
printf("%08lld",p[i]);
}
printf("\n");
return 0;
}