codeforces 559 C. Gerald and Giant Chess
有一个h行w列的棋盘,里面有一些格子是不能走的,现在要求从左上角走到右下角的方案数(只能向右或者向下走)。
1 ≤ h, w ≤ 105, 1 ≤ n ≤ 2000
如果 n ≤ 20 的话,有一个经典的容斥做法:不经过坏点的方案数=总方案数-经过1个坏点的方案数+经过2个坏点的方案数-...+(-1)n经过n个坏点的方案数
但是发现 n 很大,但在此题中,方案是有序的(类似于DAG),那么可以这么构造
对于第 i 个坏点,设 f[i] 表示从左上角走到 i 的方案数,且 i 是唯一经过的坏点
那么可以用左上角到第 i 个坏点的方案数,减去它左上角的所有坏点的 f[j] × (从 i 到 j 的方案数)
这样的话,对于每一条从左上角到 i 且第一次经过的坏点非 i 的方案全部在第一次经过的坏点处被删除掉了,因此统计不重不漏
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 2010, p = 1e9 + 7; 4 int h, w, n, f[N], fac[200010], inv[200010]; 5 int pw(int a, int b) { 6 int r = 1; 7 for( ; b ; b >>= 1, a = 1ll * a * a % p) if(b & 1) r = 1ll * r * a % p; 8 return r; 9 } 10 int C(int n, int m) { 11 return 1ll * fac[n + m] * inv[n] % p * inv[m] % p; 12 } 13 struct P { 14 int x, y; 15 } pt[N]; 16 bool operator < (P a, P b) { return a.x == b.x ? a.y < b.y : a.x < b.x; } 17 int main() { 18 scanf("%d%d%d", &h, &w, &n); 19 for(int i = fac[0] = 1 ; i <= h + w ; ++ i) fac[i] = 1ll * fac[i - 1] * i % p, inv[i] = pw(fac[i], p - 2); 20 for(int i = 1 ; i <= n ; ++ i) scanf("%d%d", &pt[i].x, &pt[i].y); 21 pt[++ n] = (P) { h, w }; 22 sort(pt + 1, pt + 1 + n); 23 inv[0] = f[0] = 1; 24 for(int i = 1 ; i <= n ; ++ i) { 25 f[i] = C(pt[i].x - 1, pt[i].y - 1); 26 for(int j = 1 ; j < i ; ++ j) { 27 if(pt[j].x <= pt[i].x && pt[j].y <= pt[i].y) { 28 f[i] = (1ll * f[i] - 1ll * f[j] * C(pt[i].x - pt[j].x, pt[i].y - pt[j].y) % p) % p; 29 } 30 } 31 } 32 printf("%d\n", (1ll * f[n] % p + p) % p); 33 }