Iroha and a Grid AtCoder - 1974【组合数学-乘法逆元-快速幂】【数学好题】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/CQBZLYTina/article/details/81945525

题意简述:

有一个 H 行 W 列的网格。

Iroha 现在站在左上角 (1, 1)。 她每次会向右或向下走,直到走到右下角 (H, W)。

唯一的限制是,她不能走到左下方的 A 行 B 列。

求行走的方案数对 10 9 + 7 取模

数据范围:

  • 1 ≦ H, W ≦ 100,000
  • 1 ≦ A < H
  • 1 ≦ B < W

思路分析:

组合计数

首先简化问题,我们先计算一个矩形,从左上角走到右下角的路径数。

//手动忽略渣图,灵魂小画手qwq
从左上角走到右下角一共需要走H-1+W-1=H+W-2步,这其中,要选H-1步往下走,W-1步往右走。
所以组合数就是C(H+W-2,H-1)=C(H+W-2,W-1)(这两个任选其一进行计算)


可是这道题有一部分是“禁区”,怎么办呢?
有两种解决办法

扫描二维码关注公众号,回复: 3777896 查看本文章
1

我们可以先计算出整个矩形的方案数,再减去不合法的方案数。
不合法的方案数怎么找呢?
只要进入了“禁区”,方案就不合法(废话!
而因为只能向右或向下,所以要进入“禁区”的方案都经过了“禁区”的最上面一排。所以我们我们在到达边界的时候就要亮起红灯(嘀嘀嘀)枚举边界上那一排点,把经过了它们的方案数全部剔掉。
Like this:(有颜色的部分是“禁区”)

如图,从红点到绿点,到黄点再到蓝点的方案数必须剔掉。
注意了,每个绿点只能走它下面的那个黄点。为什么?
如果不走下面,它就不会到达“禁区”。

那为什么要用两排点?
看下面(这是我无数次不得其解的血泪史qwq):

Notice:下面这张图是不合法的!!!
必须要走到上一行再走它的下一行,直接这样会导致重复!!!(啊啊啊,我再这里卡了很久啊)
举个栗子,如果像下图那样计算,从红点到绿点1,再从绿点1到蓝点时,会有一些方案是从绿点1经过绿点2的;然后后面来计算红点到绿点2,再从绿点2到蓝点时,就会重复!!!(红点到绿点2的路径也可以经过绿点1)

所有从红点走到绿点,再从绿点走到蓝点的方案都不合法

那如果在这个图下面加一排黄点呢?干嘛非要让绿点在H-A排?就像现在这样在H-A+1排也可以吧?(啊喂你是十万个为什么吗)
答案是不行!
如果那样的话,会少一些情况:就是只经过了第一排“禁区”的情况。
比如说这个:

那搞定了所有的“十万个为什么”之后,下面来计算一下方案数:
上面的那个矩形一共走H-A+i-2,向下走,下面的那个矩形一共走A-1+W-i,向下走A-1
所以就是:

ans-=C(H-A+i-2,H-A-1)*C(A-1+W-i,A-1)
2

我们可以把这个矩形进行拆分,把它变成两个矩形,把“禁区”排斥在外,将2个小矩形的方案数相乘。拆分时枚举“断点”
Like this:

其它的都跟上面那一种是一样的。
这两种思维模式的不同在于:一个是求出所有方案数,减去不合法的(有点容斥的意味),另一个是直接求合法方案,把不合法的摒除在外。

那这一部分的方案数的计算
上面的小矩形,总步数是H-A+i-2,向右走i-1,下面的小矩形,总步数是A+W-i-1,向下走A-1步。

乘法逆元

以上,这道题的大部分地方就ok啦,但是还有一个头疼的东西。
就是这道题要mod 10^9+7
(P.S. 写#define MOD 最好后面跟 1000000007,1e9+7有时候有点玄学)
而我们知道,组合数是有除法的,而且还是阶乘的除法,而模运算对于除法没有封闭性(不满足分配率)

所以绕了这么多,还是要搞乘法逆元啊qwq
这是我第一次碰见乘法逆元的题,东西就顺便写在这儿吧。

逆元的定义

什么是逆元呢(搜狗输入法居然没有逆元这个词组(自动跳出来))

继承数学老师的衣钵,逆元的定义是关系类的定义,不会说谁是逆元,而是谁和谁互为逆元,并且这个定义是在mod m下的。

逆元的求法

比较经典的就是用费马小定理,经不经典我不知道,但是我只会这一种qwq


快速幂

这个其实就比较简单了,利用了二进制来进行优化
比如我们要求2^13
朴素的算法就是将2连乘13次
而快速幂是这么做的:13的二进制是1101 那么 2^13=2^1 * 2^4 * 2^8 就只需要计算这些值就可以了 实现时用了一个累乘器,每次判断如果这个数的最右是1的话就让ans乘一下累成器
快速幂可以降到log的级别

LL Pow(LL a,int b) 
{
    LL ans=1;
    while(b) 
    {
        if(b&1) ans=ans*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return ans;
}

最后就是代码君啦:

code view

#include<cstdio>
#include<algorithm>
using namespace std;
#define MOD 1000000007
#define MAXN 200005
#define LL long long
LL inv[MAXN],s[MAXN];
int N;
int H,W,A,B;
LL Pow(LL a,int b) 
{
    LL ans=1;
    while(b) 
    {
        if(b&1) ans=ans*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return ans;
}
void Pre() 
{
    s[0]=1;
    for(int i=1; i<=N; i++)
        s[i]=s[i-1]*i%MOD;
    inv[0]=1;
    inv[N]=Pow(s[N],MOD-2);
    for(int i=N-1; i>0; i--)
        inv[i]=inv[i+1]*(i+1)%MOD;
}
LL C(int a,int b) 
{
    if(a<0||b<0) return 1;
    return s[a]*inv[b]%MOD*inv[a-b]%MOD;
}
int main() 
{
    scanf("%d %d %d %d",&H,&W,&A,&B);
    N=H+W-2;
    Pre();
    LL ans=C(H+W-2,H-1);
    for(int i=1;i<=B;i++)
    {
        ans-=C(H-A+i-2,H-A-1)*C(A-1+W-i,A-1)%MOD;
        ans=(ans%MOD+MOD)%MOD;
    }
    printf("%lld\n",ans);
}

另一种写法

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define MOD 1000000007
#define LL long long
int H,W,A,B,N;
int a[MAXN];
LL s[MAXN],inv[MAXN];
LL Pow(LL a,int b)//快速幂
{
    LL ans=1;
    while(b)
    {
        if(b&1) ans=ans*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return ans;
}

void Pre()
{
    s[0]=1;
    for(int i=1;i<=N;i++)
        s[i]=s[i-1]*i%MOD;   //阶乘
    inv[0]=1;
    inv[N]=Pow(s[N],MOD-2);
    for(int i=N-1;i>0;i--)
        inv[i]=inv[i+1]*(i+1)%MOD;
}

LL C(int a,int b)
{
    if(a<0||b<0) return 1;
    return s[a]*inv[b]%MOD*inv[a-b]%MOD;
}
int main()
{
    scanf("%d %d %d %d",&H,&W,&A,&B);
    N=H+W-2;
    Pre();
    LL ans=0;
    for(int i=B+1;i<=W;i++)
    {
        ans+=C(H-A+i-2,i-1)*C(A+W-i-1,A-1)%MOD;
        ans=(ans%MOD+MOD)%MOD;
    }
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/CQBZLYTina/article/details/81945525
今日推荐