首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/881/E
来源:牛客网
涉及:排列组合数学
点击这里回到2019牛客暑期多校训练营解题—目录贴
题目如下:
走网格?!好吧,这个题真的就是普通的走网格
很明显,题目中说明了字符串肯定有
(
n
+
m
)
(n+m)
(n+m)个字符’A’和
(
n
+
m
)
(n+m)
(n+m)个字符’B’,我们可以把这个题往走网格的方面想,如图所示:
开始一个字符串是空的,从原点开始,每往上走一步,字符串就加一个B,每往右走一步,字符串就加一个A。
很明显,走到坐标为 ( n + m , n + m ) (n+m,n+m) (n+m,n+m)的时候,字符串里面刚好有 ( n + m ) (n+m) (n+m)个A和 ( n + m ) (n+m) (n+m)个B
所以,由 ( n + m ) (n+m) (n+m)个A和 ( n + m ) (n+m) (n+m)个B组成的字符串的数量,刚好是走网格从原点走到坐标为 ( n + m , n + m ) (n+m,n+m) (n+m,n+m)的路径数量
由于只能往上或者往右走,一共需要走 2 ∗ ( n + m ) 2*(n+m) 2∗(n+m)步才能到达 ( n + m , n + m ) (n+m,n+m) (n+m,n+m),其中有 ( n + m ) (n+m) (n+m)步向上走, ( n + m ) (n+m) (n+m)步想右走。
所以从原点到
(
n
+
m
,
n
+
m
)
(n+m,n+m)
(n+m,n+m)的路径数,根据排列组合可知数量为
C
2
(
n
+
m
)
n
+
m
C_{2(n+m)}^{n+m}
C2(n+m)n+m
但是根据题目的要求,字符串必须完美拆分成’AB’或者’BA’,所以,在走网格中,有些路径点是不能走的,现在就来讨论一下,哪些路径点不能走。
如题所示,有n个’AB’和m个’BA’。也就说说,这个字符串不可能出现下面的情况
A
A
A
A
.
.
.
A
A
A
A
⎵
(
n
+
1
)
个
A
B
A
B
B
.
.
.
B
A
B
A
⎵
任
意
序
列
\begin{matrix}\underbrace{AAAA...AAAA}\\(n+1)个A\end{matrix}\begin{matrix}\underbrace{BABB...BABA}\\任意序列\end{matrix}
AAAA...AAAA(n+1)个A
BABB...BABA任意序列
很明显,这种情况的话,字符串一定会拆分出
(
n
+
1
)
(n+1)
(n+1)个’AB’,但是题目要求只能拆分出
n
n
n个’AB’,所以这种情况错误。
也就是说,再未向上走(加一个B)的情况下,是不能一开始就向右走
(
n
+
1
)
(n+1)
(n+1)步(连续加
(
n
+
1
)
(n+1)
(n+1)个A)
同理,下面这种情况也不行
B
A
A
A
A
.
.
.
A
A
A
A
⎵
(
n
+
2
)
个
A
A
B
B
.
.
.
B
B
A
B
⎵
任
意
序
列
\begin{matrix}B\underbrace{AAAA...AAAA}\\(n+2)个A\end{matrix}\begin{matrix}\underbrace{ABB...BBAB}\\任意序列\end{matrix}
B
AAAA...AAAA(n+2)个A
ABB...BBAB任意序列
这种情况也是不行的,虽然开头的B能和后面的A组成一个’BA’,但是后面剩下的
(
n
+
1
)
(n+1)
(n+1)个A还是会拆分出
(
n
+
1
)
(n+1)
(n+1)个’AB’,也不满足题目情况。
寻找规律我们会发现,如图所示,红色部分的路径点是不能走的(网格自动忽略)
同理,还有一块红色区域也不能走
现在,需要求出从原点经过红色部分到达 ( n + m , n + m ) (n+m,n+m) (n+m,n+m)的路径数量,然后用总的路径数减去进过红色部分的,即为正确答案。
根据对称,如图所示,从原点经过红色部分到达
(
n
+
m
,
n
+
m
)
(n+m,n+m)
(n+m,n+m)的路径数,等于从
A
1
(
−
m
−
1
,
m
+
1
)
A_1(-m-1,m+1)
A1(−m−1,m+1)到
(
n
+
m
,
n
+
m
)
(n+m,n+m)
(n+m,n+m)与
A
2
(
n
+
1
,
−
n
−
1
)
A_2(n+1,-n-1)
A2(n+1,−n−1)到
(
n
+
m
,
n
+
m
)
(n+m,n+m)
(n+m,n+m)的路径数之和。
如图,从O经过红色区域到达
(
n
+
m
,
n
+
m
)
(n+m,n+m)
(n+m,n+m)(相当于从A1到
(
n
+
m
,
n
+
m
)
(n+m,n+m)
(n+m,n+m)和从A2到
(
n
+
m
,
n
+
m
)
(n+m,n+m)
(n+m,n+m)之和)路径数为
C
2
(
n
+
m
)
m
−
1
+
C
2
(
n
+
m
)
n
−
1
C_{2(n+m)}^{m-1}+C_{2(n+m)}^{n-1}
C2(n+m)m−1+C2(n+m)n−1
所以合法路径数为
C
2
(
n
+
m
)
n
+
m
−
C
2
(
n
+
m
)
m
−
1
−
C
2
(
n
+
m
)
n
−
1
C_{2(n+m)}^{n+m}-C_{2(n+m)}^{m-1}-C_{2(n+m)}^{n-1}
C2(n+m)n+m−C2(n+m)m−1−C2(n+m)n−1
即为答案。
一点点小建议,由于代码实现排列组合会涉及阶乘和分数取模,可以预处理得到阶乘的值和逆元的值,在后面的case中可以O(1)得到答案。
注意:0的阶乘和逆元都是1
最后膜一发队友的脑回路。
举个例子
举啥例子啊,样例答案不就是
C
6
3
−
C
6
1
−
C
6
0
=
13
C_6^3-C_6^1-C_6^0=13
C63−C61−C60=13
代码如下
#include <iostream>
#include <assert.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int n,m;
ll fk[4005],inv[4005];//fk[i]为i的阶乘,inv[i]为i在模上mod下的逆元
ll qpow(ll x){//快速幂求逆元
ll pow=mod-2,sum=1;
while(pow){
if(pow%2==1) sum=sum*x%mod;
pow>>=1;
x=x*x%mod;
}
return sum;
}
ll C(ll a,ll b){return fk[a]*inv[b]%mod*inv[a-b]%mod;}//这个函数是求组合的值
int main(){
int i;
fk[0]=inv[0]=1;
for(i=1;i<=4000;i++) fk[i]=i*fk[i-1]%mod,inv[i]=qpow(fk[i]);//预处理得到两个数组的值
while(~scanf("%d%d",&n,&m)){
ll ans=C(2*(n+m),n+m)-C(2*(n+m),m-1)%mod-C(2*(n+m),n-1);//套公式
printf("%lld\n",(ans%mod+mod)%mod);//取模求解
}
}