Gerald and Giant Chess组合数容斥+dp
经典的走格子问题,从(1,1)到(n,m)的种类数,传统n²做法,f[i][j] = f[i][j - 1] + f[i - 1][j],但是这题数据比较大,组合数可以用费马小定理求,经典的组合数公式,2e5的组合数,预处理求即可,关键是怎么去掉那些不能走的格子的方案数。
传送门
Solution
考虑只有一个黑点情况,我们到这个黑点的种类数不能计算,减掉即可。多个同理,但是我们得保证计算去掉当前黑点,位于左上方的点,都得计算完毕,我们给他按照x,y升序排列,f[i]表示到i这个黑点可走的种类数,++k,把(n, m)当成最后一个点,最后的f[k]就是方案数。特别注意这个公式:(1,1)->(n, m)的种类数为C(n + m -2, n -1)
Code
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <vector>
#include <queue>
#include <algorithm>
#define x first
#define y second
#define int long long
#define endl "\n"
#define pii pair<int, int>
#define RE(i,a,b) for(int i = a; i <= b; ++i)
using namespace std;
template <typename T> inline void read(T& t) {
int f = 0, c = getchar();
t = 0;
while (!isdigit(c)) f |= c == '-', c = getchar();
while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
if (f) t = -t;
}
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
int fac[N], inv[N];
struct node {
int x, y;
bool operator < (const node& rhs) const {
return x == rhs.x ? y < rhs.y : x < rhs.x;
}
} a[2010];
int n, m, k;
int f[N];
int qk(int a, int b) {
int ans = 1;
a %= mod;
while(b) {
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
void init() {
fac[0] = 1;
for(int i = 1; i < N; i++)fac[i] = fac[i - 1] * i % mod;
inv[N- 1] = qk(fac[N - 1], mod - 2);
for(int i = N - 2; i >= 0; i--)inv[i] = (i + 1) * inv[i + 1] % mod;
}
int C(int a, int b) {
if(b < 0 || b > a) return 0;
return fac[a] * inv[a - b] % mod * inv[b] % mod;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0);
init();
cin >> n >> m >> k;
RE(i, 1, k) cin >> a[i].x >> a[i].y;
a[++k] = {n, m};
sort(a + 1, a + 1 + k);
RE(i, 1, k) {
f[i] = C(a[i].x + a[i].y - 2, a[i].x - 1);//计算到该点的种类数
for(int j = 1; j < i; ++j) {
if(a[j].x <= a[i].x && a[j].y <= a[i].y) {//如果完全在左上方,就挖掉这些种类数
f[i] = (f[i] - f[j] * C(a[i].x - a[j].x + a[i].y - a[j].y, a[i].x - a[j].x) % mod + mod) % mod;
}
}
}
cout << f[k];
return 0;
}