BZOJ3907 网格

题目:BZOJ3907: 网格

思路:

显然,这道题是卡特兰数经典模型的变式。
假设不考虑越界限制,从(0,0)到(n,m)的总方案数为\(C_{n+m}^n\),如果能计算出其中有哪些是不合法的,二者相减即可。
我们可以用一个1和-1的串记录行走的路径,记向右走为+1,向上走为-1,则一条合法路径满足串中任意位置的前缀和均不小于0。串长为n+m,其中有n个1,m个-1,所以串的总数有\(C_{n+m}^n\)
对于一条不合法路径,我们找到第一个前缀和小于0的位置pos,把串从1到pos位置翻转(1->-1,-1->1),那么最后串长不变,pos位之后的串不变,1~pos位中1的个数多了1,-1的个数少了1,最后总串长不变。因此每个不合法方案均能唯一映射到这种串,不合法方案总数:\(C_{n+m}^{n+1}\)
几何意义:在网格图中,对于不合法的方案,一定经过直线y=x+1。把起点到第一个触碰到直线y=x+1的点之间走过的路径沿y=x+1对称,第一次触碰之后的路径不变。所以每条不合法方案都对应一条从(-1,1)到(n,m)的路径。
最后答案为:\(C_{n+m}^n-C_{n+m}^{n+1}\)
注意到没有模数,所以需要高精度。组合数计算时需要用到除法,直接写高精除高精效率比较低,只能得60pts。可以对分子分母同时分解质因数之后计算,省去除法。


Code:

#include <bits/stdc++.h>
using namespace std;
const int N=10000,base=10000,power=4;
int n,m,cnt[N],mindiv[N],p[N],tot;
struct bigint{
    int d[N],len;
    inline void clean() {while(len>1&&!d[len]) --len;} 
    inline bigint(){memset(d,0,sizeof(d)),len=1;}
    inline bigint(int num) {len=1;d[1]=num;}
    bigint operator - (const bigint &b)const{
        bigint c=*this;
        for(int i=1;i<=b.len;++i){
            c.d[i]-=b.d[i];
            if(c.d[i]<0) c.d[i]+=base,--c.d[i+1];
        }
        c.clean();
        return c;
    }
    bigint operator * (const bigint &b)const{
        bigint c;
        for(int i=1;i<=len;++i) for(int j=1;j<=b.len;++j)
            c.d[i+j-1]+=d[i]*b.d[j],c.d[i+j]+=c.d[i+j-1]/base,c.d[i+j-1]%=base;
        c.len=len+b.len;
        c.clean();
        return c;   
    }
    void print(){
        clean();
        printf("%d",d[len]);
        for(int i=len-1;i>=1;--i) printf("%0*d",power,d[i]);
    }
};
void Prime(){
    for(int i=2;i<=n+m;++i){
        if(!mindiv[i]) mindiv[i]=p[++tot]=i;
        for(int j=1;j<=tot;++j){
            if(i*p[j]>m+n||p[j]>mindiv[i]) break;
            mindiv[i*p[j]]=p[j];
        }
    }
}
void add(int num){
    while(num^1){
        ++cnt[mindiv[num]];
        num/=mindiv[num];
    }
}
void del(int num){
    while(num^1){
        --cnt[mindiv[num]];
        num/=mindiv[num];
    }
}
bigint quickpow(int a,int b){
    bigint res=1,c=a;
    while(b){
        if(b&1) res=res*c;
        c=c*c;
        b>>=1;
    }
    return res;
}
bigint C(int n,int m){
    bigint ans=1;
    for(int i=1;i<=tot;++i) cnt[p[i]]=0;
    for(int i=n-m+1;i<=n;++i) add(i);
    for(int i=1;i<=m;++i) del(i);
    for(int i=1;i<=tot;++i) ans=ans*quickpow(p[i],cnt[p[i]]);
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    Prime();
    (C(n+m,m)-C(n+m,m-1)).print();
    return 0;
}

转载于:https://www.cnblogs.com/yu-xing/p/11221901.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值