有一个 H 行 W 列的网格。
Iroha 现在站在左上角 (1, 1)。 她每次会向右或向下走,直到走到右下角 (H, W)。
唯一的限制是,她不能走到左下方的 A 行 B 列。
求行走的方案数对 10 9 + 7 取模
数据范围:
首先简化问题,我们先计算一个矩形,从左上角走到右下角的路径数。
//手动忽略渣图,灵魂小画手qwq
从左上角走到右下角一共需要走H-1+W-1=H+W-2步,这其中,要选H-1步往下走,W-1步往右走。
所以组合数就是C(H+W-2,H-1)=C(H+W-2,W-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个小矩形的方案数相乘。拆分时枚举“断点”
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 那么 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;
}
最后就是代码君啦:
#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;
}