题目
题目大意
一个 H×W H × W 的矩形,左下角的 A×B A × B 的矩形区域不能走,问左上角走到右下角的方案数模 109+7 10 9 + 7 。
分析
大题分析 - 组合数学
如果无视障碍,从 (x,y) ( x , y ) 走到 (m,n) ( m , n ) 的方案数是 Cn−ym−x+n−y C m − x + n − y n − y 。
用动规的思想:
dp[i][j]=dp[i−1][j]+dp[i][j−1]
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
]
[
j
−
1
]
,从
(1,1)
(
1
,
1
)
到每个格子的方案数如下:
发现把它歪过来就是杨辉三角的样子,事实上,这个转移方程就是杨辉三角的转移方程,即
Cnm=Cnm−1+Cn−1m
C
m
n
=
C
m
−
1
n
+
C
m
n
−
1
,如果要从杨辉三角上看组合数的话就是这个样子:
C00=1
C
0
0
=
1
,
C01=1
C
1
0
=
1
,
C11=1
C
1
1
=
1
,
C02=1
C
2
0
=
1
,
C12=2
C
2
1
=
2
,
C22=1
C
2
2
=
1
……注意是从
0
0
开始的。
从这里面框出一个平行四边形区域就是这道题的模样了:
所以从走到
(x,y)
(
x
,
y
)
的方案数是
Cy−1x+y−1−1
C
x
+
y
−
1
−
1
y
−
1
(在杨辉三角中数出来的行数和列数都要减一,才是组合数的答案)即
Cy−1x+y−2
C
x
+
y
−
2
y
−
1
。由于从
(x,y)
(
x
,
y
)
走到
(m,n)
(
m
,
n
)
的方案数就是从
(1,1)
(
1
,
1
)
走到
(m−x+1,n−y+1)
(
m
−
x
+
1
,
n
−
y
+
1
)
的方案数,而后者是
C(n−y+1)−1(m−x+1)+(n−y+1)−2=Cn−ym−x+n−y
C
(
m
−
x
+
1
)
+
(
n
−
y
+
1
)
−
2
(
n
−
y
+
1
)
−
1
=
C
m
−
x
+
n
−
y
n
−
y
,最开头的结论得证。
枚举 B+1≤i≤W B + 1 ≤ i ≤ W ,由于需要烧过左下角的区域,则每种方案必定经过一个格子 (H−A,i) ( H − A , i ) ,那么我们就将 (1,1) ( 1 , 1 ) 到 (H,W) ( H , W ) 分成了两步: (1,1) ( 1 , 1 ) 到 (H−A,i) ( H − A , i ) , (H−A+1,i) ( H − A + 1 , i ) 到 (H,W) ( H , W ) (注意第二步的开始点是 (H−A+1,i) ( H − A + 1 , i ) ,否则对于样例1就会出问题,这点我想了)。所以:
细节 - 逆元
组合数的计算公式:
Cnm=m!n!(m−n)!
C
m
n
=
m
!
n
!
(
m
−
n
)
!
。
有除法,又要模
109+7
10
9
+
7
,除法没有分配率,是不是凉了_(:з」∠)_,由于组合数计算一定能整除,所以用逆元转化一下即可,关于逆元:广告传送门。
所以
Cnm%M=m!⋅n!−1⋅(m−n)!−1%M
C
m
n
%
M
=
m
!
·
n
!
−
1
·
(
m
−
n
)
!
−
1
%
M
。妈妈再也不用担心我的除法取模了
代码
将阶乘和逆元初始化出来。
逆元可以用递推的方式求。
这里的 −1 − 1 次方和分数线只是一个记号,但是有同样的分式的性质。
所以计算出 inv[n!] 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);
}