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

文章目录

      • 题意简述:
      • 思路分析:
        • 组合计数
          • 1
          • 2
        • 乘法逆元
          • 逆元的定义
          • 逆元的求法
        • 快速幂
        • code view

题意简述:

有一个 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)(这两个任选其一进行计算)

Iroha and a Grid AtCoder - 1974【组合数学-乘法逆元-快速幂】【数学好题】_第1张图片


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

1

我们可以先计算出整个矩形的方案数,再减去不合法的方案数。
不合法的方案数怎么找呢?
只要进入了“禁区”,方案就不合法(废话!
而因为只能向右或向下,所以要进入“禁区”的方案都经过了“禁区”的最上面一排。所以我们我们在到达边界的时候就要亮起红灯(嘀嘀嘀)枚举边界上那一排点,把经过了它们的方案数全部剔掉。
Like this:(有颜色的部分是“禁区”)
Iroha and a Grid AtCoder - 1974【组合数学-乘法逆元-快速幂】【数学好题】_第2张图片
如图,从红点到绿点,到黄点再到蓝点的方案数必须剔掉。
注意了,每个绿点只能走它下面的那个黄点。为什么?
如果不走下面,它就不会到达“禁区”。

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

Notice:下面这张图是不合法的!!!
必须要走到上一行再走它的下一行,直接这样会导致重复!!!(啊啊啊,我再这里卡了很久啊)
举个栗子,如果像下图那样计算,从红点到绿点1,再从绿点1到蓝点时,会有一些方案是从绿点1经过绿点2的;然后后面来计算红点到绿点2,再从绿点2到蓝点时,就会重复!!!(红点到绿点2的路径也可以经过绿点1)
Iroha and a Grid AtCoder - 1974【组合数学-乘法逆元-快速幂】【数学好题】_第3张图片

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

那如果在这个图下面加一排黄点呢?干嘛非要让绿点在H-A排?就像现在这样在H-A+1排也可以吧?(啊喂你是十万个为什么吗)
答案是不行!
如果那样的话,会少一些情况:就是只经过了第一排“禁区”的情况。
比如说这个:
Iroha and a Grid AtCoder - 1974【组合数学-乘法逆元-快速幂】【数学好题】_第4张图片

那搞定了所有的“十万个为什么”之后,下面来计算一下方案数:
上面的那个矩形一共走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:
Iroha and a Grid AtCoder - 1974【组合数学-乘法逆元-快速幂】【数学好题】_第5张图片

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

那这一部分的方案数的计算
上面的小矩形,总步数是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

Iroha and a Grid AtCoder - 1974【组合数学-乘法逆元-快速幂】【数学好题】_第6张图片

快速幂

这个其实就比较简单了,利用了二进制来进行优化
比如我们要求2^13
朴素的算法就是将2连乘13次
而快速幂是这么做的:13的二进制是1101 那么 213=21 * 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
#include
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
#include
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;
}

你可能感兴趣的:(数学,数论)