挺巧妙的一种写法,但本质上也是个容斥吧。
看到这个东西一眼容斥 DP,但是 wtcl 咕咕咕不出来。
先把所有横纵坐标 $-1$,方便后续的计算。(其实没差)
显然 $(0,0) \rightarrow (x,y)$ 的方案数是 $C_{x+y}^{x}$($x+y$ 步选择 $x$ 步往上走)。
同理 $(x_1,y_1) \rightarrow (x_2,y_2)$ 就是坐标差组合数。
由于点对之间没有顺序关系,所以先把点排序。
设 $dp_i$ 表示从 $(0,0) \rightarrow (x_i,y_i)$ 不经过任何一个其它关键点的方案数。
枚举 $dp_j$ 满足 $(x_j,y_j)$ 在 $(x_i,y_i)$ 左上角(即 $j$ 可达 $i$),用总数减去所有 $j \rightarrow i$ 的方案数乘 $dp_j$ 就可以得到 $dp_i$。
注意阶乘和逆元要开两倍空间。
#include <bits/stdc++.h>
using namespace std;
const int N = 3015, M = 2e5 + 15, mod = 1e9 + 7;
int n, m, k;
pair<int, int> a[N];
int x[N], y[N];
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = (res * 1ll * a) % mod;
a = (a * 1ll * a) % mod;
k >>= 1;
}
return res;
}
int fac[M], inv[M];
void init() {
fac[0] = 1;
for (int i = 1; i <= 200000; i++) fac[i] = (fac[i - 1] * 1ll * i) % mod;
inv[200000] = qmi(fac[200000], mod - 2);
for (int i = 199999; i >= 0; i--) inv[i] = (inv[i + 1] * 1ll * (i + 1)) % mod;
}
int C(int n, int m) {
return fac[n] * 1ll * inv[m] % mod * 1ll * inv[n - m] % mod;
}
int cnt(int x, int y) {
return C(x + y, x);
}
long long dp[N];
int main() {
scanf("%d%d%d", &n, &m, &k);
init();
for (int i = 1; i <= k; i++) scanf("%d%d", &a[i].first, &a[i].second);
a[++k] = make_pair(n, m);
sort(a + 1, a + 1 + k);
for (int i = 1; i <= k; i++) x[i] = a[i].first - 1, y[i] = a[i].second - 1;
for (int i = 1; i <= k; i++) {
long long res = cnt(x[i], y[i]);
for (int j = 1; j < i; j++) {
if (x[j] > x[i] || y[j] > y[i]) continue;
res -= cnt(x[i] - x[j], y[i] - y[j]) * 1ll * dp[j] % mod;
res = (res + mod) % mod;
}
dp[i] = res;
}
printf("%lld\n", dp[k]);
return 0;
}