卡特兰数 + 高精度乘法压位:

总结:当前所见到的应用卡特兰数的题型大致都是这么一种。

给你两个操作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;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值