acwing算法提高课1315. 网格 题解

1315. 网格 题解

卡特兰数的几何意义:
在网格图中,从坐标(0,0)走到坐标(n,n)每次向上或者向右走一个单位,且不经过y=x这条线的方案数

如何求卡特兰数
第n项卡特兰数就是以上问题的解

如果不考虑是否越过y=x这条线,从起点到终点,只需要从2n个步骤中选n个向上走,其余向右走,就是方案数
C 2 n n C^{n}_{2n} C2nn

  1. 画一条y=x+1的线,只要是越过y=x这条线的路径,一定会走到y = x + 1上或者越过这条线,

  2. 求出(n,n)关于y=x+1的对称点
    首先将整个方格向下移动一格,n,n变为(n,n-1),y=x+1变为y=x,
    所以(n,n-1)相对于y=x对称的点为(n-1,n)
    然后将整个方格向上移动一格复原,得出n,n关于y=x+1对称的点为(n-1,n+1)

  3. 所有经过或越过y=x+1的路径,找到第一次经过y=x+1的点,从这个点开始关于y=x+1
    将这条路径对折,一定会走到(n-1,n+1)

由于所有非法路径一定经过y=x+1,所以一定会有一条相应的路径走到(n-1,n+1)
路径条数为:
C 2 n n + 1 C^{n+1}_{2n} C2nn+1

所有方案减去非法方案就是合法方案数量

C 2 n n − C 2 n n + 1 C_{2n}^{n} - C_{2n}^{n+1} C2nnC2nn+1
= = =
1 / ( n + 1 ) ∗ C 2 n n 1/(n+1) * C^{n}_{2n} 1/(n+1)C2nn
在这里插入图片描述

在本题中,求从(0,0)走到(n,m)的方案数,可以类比过来
得到所有方案数为
C n + m n C_{n+m}^{n} Cn+mn
相当于从所有操作中选择n个向右的操作,其余向上,总能走到(n,m)

本题同样要求不能越过y=x这条线
做一条y=x+1的线,对于所有的不合法方案,一定会经过这条线,

使用类似的方法得出(n,m)关于y=x+1对称的点为(m-1,n+1)
所有到达(m-1,n+1)的点的路径对应一个非法方案,方案数为
C m + n n + 1 C_{m+n}^{n+1} Cm+nn+1

所有合法方案数为
C n + m n − C m + n n + 1 C_{n+m}^{n}-C_{m+n}^{n+1} Cn+mnCm+nn+1

( m + n ) ! / ( n ! ) ( m ! ) − ( m + n ) ! / ( n + 1 ) ! ( m − 1 ) ! (m+n)!/(n!)(m!) - (m+n)!/(n+1)!(m-1)! (m+n)!/(n!)(m!)(m+n)!/(n+1)!(m1)!
化简为
( m + n ) ! ( n + 1 − m ) / ( n + 1 ) ! m ! (m+n)!(n+1-m)/(n+1)!m! (m+n)!(n+1m)/(n+1)!m!

使用朴素的高精度+分解质因数求出该式子的结果,就是答案

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 10010;

int prime[N], cnt;
bool st[N];
int n, m;
int a[N], al;
int sum[N];

void init(int n){
    for(int i = 2; i <= n; i ++){
        if(!st[i]) prime[cnt ++] = i;
        for(int j = 0; j < cnt && prime[j] * i <= n; j ++){
            st[prime[j] * i] = 1;
            if(i % prime[j] == 0) break;
        }
    }
}

int get(int p, int n){
    int res = 0;
    for(;n;n /= p) res += n / p;
    return res;
}

void mul(int a[], int b, int &len){
    int r = 0;
    for(int i = 0; i < len; i ++){
        r += a[i] * b;
        a[i] = r % 10;
        r /= 10;
    }
    while(r){
        a[len ++] = r % 10;
        r /= 10;
    }
}

int main(){
    init(N - 1);
    cin >> n >> m;
    for(int i = 0; i < cnt; i ++){
        int p = prime[i];
        sum[i] = get(p, m + n) - get(p, n + 1) - get(p, m);
    }
    a[0] = 1;
    al = 1;
    for(int i = 0; i < cnt; i ++){
        int p = prime[i];
        int s = sum[i];
        while(s --) mul(a, p, al);
    }
    mul(a, n - m + 1, al);
    int t = al;
    while(!a[t]) t --;

    for(int i = t; i >= 0; i --) cout << a[i] ;
    
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值