[BZOJ 3907] [JZOJ 3431] 网格

Description

某城市的街道呈网格状,左下角坐标为A(0, 0),右上角坐标为B(n, m),其中n >= m。现在从A(0, 0)点出发,只能沿着街道向正右方或者正上方行走,且不能经过图示中直线左上方的点,即任何途径的点(x, y)都要满足x >= y,请问在这些前提下,到达B(n, m)有多少种走法。
这里写图片描述

100%的数据中,1 <= m <= n <= 5 000

Analysis

因为我是蒟蒻,所以这道题我是暴力找规律,找着找着突然发现好像是两个组合数相减。。。
但是比赛的时候没有打完。
好吧,其实可以推出来。
将直线向上平移一格, 则限制条件等价于不能碰到直线。
那么我们将终点按这条直线对称过去,找到一个对称点。
我们发现把起点走到对称点的路径再对称回来,路径即是一条不合法的路径。
所以方案数可以由无限制乱走的方案数-起点走到对称点的方案数。
推一推,就发现方案数就是组合数。(因为dp是杨辉三角)
最后答案为 Cmn+mCm1n+m
但是看一下数据范围,并且题目没有要求取余数,所以我们要打高精度了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=5010;
int n,m,a[N*2];
struct bignum
{
    int n,a[N];
    bignum(){}
    bignum(int jy)
    {
        memset(a,0,sizeof(a));
        a[n=1]=jy;
    }
}c,e;
void pre(int n,int z)
{
    int x=n;
    for(int i=2;i*i<=n;i++)
        while(!(x%i))
        {
            x/=i;
            a[i]+=z;
        }
    if(x>1) a[x]+=z;
}
void mul(bignum &c,bignum a,bignum b)
{
    c=bignum(0);
    c.n=a.n+b.n-1;
    fo(i,1,a.n)
        fo(j,1,b.n)
        {
            c.a[i+j-1]+=a.a[i]*b.a[j];
            c.a[i+j]+=c.a[i+j-1]/10;
            c.a[i+j-1]%=10;
        }
    while(c.a[c.n+1])
    {
        c.n++;
        c.a[c.n+1]=c.a[c.n]/10;
        c.a[c.n]%=10;
    }
}
void qmi(bignum &a,int x,int n)
{
    e=bignum(x);
    for(;n;n>>=1)
    {
        if(n&1) mul(a,a,e);
        mul(e,e,e);
    }
}
int main()
{
    //the answer is c[n+m][m]-c[n+m][m-1]
    scanf("%d %d",&n,&m);
    pre(n-m+1,1);
    fo(i,m+1,n+m) pre(i,1);
    fo(i,2,n+1) pre(i,-1);
    c=bignum(1);
    fo(i,2,10000)
        if(a[i]) qmi(c,i,a[i]);
    fd(i,c.n,1) printf("%d",c.a[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值