计数(count)
【题目描述】
Alice 和 Bob 在平面直角坐标系中下棋。 Alice 的棋子初始时在(0, 0)位置, 要走到(a, b)位
置; Bob 的棋子初始时在(c, 0)位置, 要走到(a, d)位置。棋子只能沿 x 轴或 y 轴正方向移动若
干个单位长度, 问有多少种移动方案使两颗棋子的移动路径不相交。
【输入数据】
输入一行 4 个正整数, 依次为 a, b, c, d。
【输出数据】
输出总方案数对质数 100000007 取模的结果。
【样例输入】
3 2 1 1
【样例输出】
6
【样例解释】
A 走(0,0) → (0,2) → (3,2) 时, B 有 3 种走法:
(1,0) → (1,1) → (3,1)
(1,0) → (2,0) → (2,1) → (3,1)
(1,0) → (3,0) → (3,1)
A 走(0,0) → (1,0) → (1,2) → (3,2)时, B 有 2 种走法。
A 走(0,0) → (1,0) → (1,1) → (2,1) → (2,2) → (3,2)时, B 有 1 种走法。
【数据范围】
对于 50%的数据, a + b <= 20。
对于 70%的数据, a + b <= 2e4。
对于 100%的数据, a + b <= 2e5 且 a > c, b > d
暴力我们两个dfs,当找到一个a路径方案时就dfs出c的所有方案,然后累加即可。
对于这道题我们可以发现A,B在同一条直线上,且A始终在B的上方。
如果我们不考虑重叠的情况,到(a,b)的方案数为
(
a
+
b
a
)
{a+b \choose a}
(aa+b)
ps:方案数后面的为美式组合数的写法,带C的是苏式的,(我没找到打出来苏式的方法),苏式和美式的上下表示的意思是反的。 ( a b ) {a\choose b} (ba)这个指在a中选b个
到(a,d)的方案数为 ( a − c + b a − c ) {a-c+b\choose a-c} (a−ca−c+b);
那么我们考虑减去相交的方案数
那相交的情况怎么处理呢?
如果两个路径相交,必然会有
≥
1
≥1
≥1的交点,同时,如果我们交换两个路径的最后一个点的后面的路径
那么新生成的两个路径就变成从原点到(a, d),从(c,0)到(a,b);
那么我们只要求出到这两个的方案数,就是相交的方案数。
关于证明:首先,如果原点目标改为(a,d),那么它的路径上的y一定不会高于d;
从(c,0)点到(a,b)的y一定会竖跨整个0 - b的y坐标,即每个点的y坐标能覆盖0-b;
而从原点开始的路径又会覆盖整个0-a的x坐标,因此他们一定相交。
又因为c路径的x不小于c,a路径的y不大于d,因此他们的交点一定出现在
(
c
,
0
)
−
(
a
,
d
)
(c,0)-(a,d)
(c,0)−(a,d)的矩阵中。
同时通过上面的路径交换我们可以发现,这两个相交的路径可以通过变换变成符题目目标要求的道路。
而所有相交的又可以反推出是更改目标的路径,因此
(
a
+
d
a
)
a + d\choose a
(aa+d) *
(
a
−
c
+
b
a
−
c
)
a - c + b\choose a-c
(a−ca−c+b)为所有x相交的方案数
然后我们拿
(
a
+
b
a
)
∗
(
a
−
c
+
d
a
−
c
)
−
(
a
+
d
a
)
∗
(
a
−
c
+
b
a
−
c
)
{a + b\choose a} * {a - c + d\choose a-c} - {a+d\choose a} * {a-c+b\choose a-c}
(aa+b)∗(a−ca−c+d)−(aa+d)∗(a−ca−c+b)就是所有合法的方案数
写这道题我学会的主要还是数论上的知识:关于逆元的知识,快速幂求逆元,线性递推求逆元
#include<bits/stdc++.h>
#define MAXN 200005
#define ll long long
using namespace std;
const ll mod = 1e8 + 7;
inline void read(int &s){
s = 0;int w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') s = s*10 + ch -'0', ch = getchar();
s *= w;
}
int a, b, c, d;
ll ans = 0;
ll fr[MAXN], inv[MAXN];//fr为预处理的阶乘,inv为逆元
inline ll pow(ll x, ll n){//快速幂求逆元
if(!n) return 1;
ll res = pow(x, n >> 1);
res = res * res % mod;
if(n & 1) res = (res * x) % mod;
return res;
}
ll C(int n, int m){//组合数
return (fr[m] % mod * inv[n]% mod * inv[m - n] % mod) % mod;
}
int main()
{
read(a), read(b), read(c), read(d);
fr[0] = 1;
for(int i = 1; i < MAXN; ++i) fr[i] = (fr[i-1]%mod * i%mod) % mod;
//求最大的阶乘的逆元
inv[MAXN - 1] = pow(fr[MAXN - 1], mod - 2);
//下面线性递推阶乘逆元
for(int i = MAXN - 1; i > 0; i--) inv[i - 1] = inv[i] * i % mod;
ans = (C(a, a + b) * C(a - c, a - c + d))%mod - (C(a, a + d) * C(a - c, a - c + b))%mod;
if(ans < 0) ans += mod;
printf("%lld\n", ans);
return 0;
}