【组合数学】【逆元】AtCoder - 1974·いろはちゃんとマス目 / Iroha and a Grid

题目

传送门

题目大意

一个 H × W 的矩形,左下角的 A × B 的矩形区域不能走,问左上角走到右下角的方案数模 10 9 + 7

分析

大题分析 - 组合数学

如果无视障碍,从 ( x , y ) 走到 ( m , n ) 的方案数是 C m x + n y n y


用动规的思想: d p [ i ] [ j ] = d p [ i 1 ] [ j ] + d p [ i ] [ j 1 ] ,从 ( 1 , 1 ) 到每个格子的方案数如下:
方案
发现把它歪过来就是杨辉三角的样子,事实上,这个转移方程就是杨辉三角的转移方程,即 C m n = C m 1 n + C m n 1 ,如果要从杨辉三角上看组合数的话就是这个样子: C 0 0 = 1 C 1 0 = 1 C 1 1 = 1 C 2 0 = 1 C 2 1 = 2 C 2 2 = 1 ……注意是从 0 开始的。
杨辉三角
从这里面框出一个平行四边形区域就是这道题的模样了:
这里写图片描述
所以从 ( 1 , 1 ) 走到 ( x , y ) 的方案数是 C x + y 1 1 y 1 (在杨辉三角中数出来的行数和列数都要减一,才是组合数的答案)即 C x + y 2 y 1 。由于从 ( x , y ) 走到 ( m , n ) 的方案数就是从 ( 1 , 1 ) 走到 ( m x + 1 , n y + 1 ) 的方案数,而后者是 C ( m x + 1 ) + ( n y + 1 ) 2 ( n y + 1 ) 1 = C m x + n y n y ,最开头的结论得证。


枚举 B + 1 i W ,由于需要烧过左下角的区域,则每种方案必定经过一个格子 ( H A , i ) ,那么我们就将 ( 1 , 1 ) ( H , W ) 分成了两步: ( 1 , 1 ) ( H A , i ) ( H A + 1 , i ) ( H , W ) (注意第二步的开始点是 ( H A + 1 , i ) ,否则对于样例1就会出问题,这点我想了)。所以:

A n s = i = B + 1 W ( C H A 1 + i 1 i 1 × C H ( H A + 1 ) + W i W i ) = i = B + 1 W ( C H A + i 1 i 1 × C A + W i 1 W i )

细节 - 逆元

组合数的计算公式: C m n = m ! n ! ( m n ) !
有除法,又要模 10 9 + 7 ,除法没有分配率,是不是凉了_(:з」∠)_,由于组合数计算一定能整除,所以用逆元转化一下即可,关于逆元:广告传送门
所以 C m n % M = m ! · n ! 1 · ( m n ) ! 1 % M 妈妈再也不用担心我的除法取模了

代码

将阶乘和逆元初始化出来。
逆元可以用递推的方式求。

i n v [ i ! ] i ! 1 1 i ! i + 1 ( i + 1 ) ! ( i + 1 ) ! 1 · ( i + 1 ) i n v [ i + 1 ] · ( i + 1 )

这里的 1 次方和分数线只是一个记号,但是有同样的分式的性质。
所以计算出 i n v [ n ! ] ,即可递推得出其他的逆元

#include<cstdio>

#define LL long long
#define MAXN 100000
#define MOD 1000000007
LL fac[2*MAXN+5];//fac[i]表示i!
LL inv[2*MAXN+5];//inv[i]表示i!的逆元

LL Pow(LL x,LL p){//快速幂求逆元
    LL ret=1;
    while(p){
        if(p&1)
            ret=ret*x%MOD;
        x=(x*x)%MOD;
        p>>=1;
    }
    return ret;
}

void Prep(int N){
    fac[0]=1;
    for(int i=1;i<=N;i++)
        fac[i]=fac[i-1]*i%MOD;//随时随地记得把模带上
    inv[0]=1;//这里要注意一下
    inv[N]=Pow(fac[N],MOD-2);
    for(int i=N-1;i>=1;i--)
        inv[i]=inv[i+1]*(i+1)%MOD;
}

LL C(int m,int n){
    return fac[m]*inv[n]%MOD*inv[m-n]%MOD;//计算组合数
}

int main(){
    int H,W,A,B;
    scanf("%d%d%d%d",&H,&W,&A,&B);
    Prep(H+W);
    LL Ans=0;
    for(int i=B+1;i<=W;i++){
        LL tmp1=C(H-A+i-2,i-1);
        LL tmp2=C(A+W-i-1,W-i);//不开long long会乘爆
        Ans=(Ans+(tmp1*tmp2)%MOD)%MOD;
    }
    printf("%lld",Ans);
}

猜你喜欢

转载自blog.csdn.net/C20190102/article/details/81177078
今日推荐