CF559C Gerald and Giant Chess
Description
给定一个 \(H \times W\) 的棋盘,棋盘上只有 \(N\) 个格子是黑色的,其他格子都是白色的。在棋盘左上角有一个卒,每一步可以向右或者向下移动一格,并且不能移动到黑色格子中。求这个卒从左上角移动到右下角,一共有多少种可能的路线,答案对 \(10^9+7\) 取模。
Constraints
\(1<=h,w<=10^{5}\) , \(1<=n<=2000\)
Solution
初看此题,我们可以考虑DP做,但发现 \(H\) 与 \(W\) 的取值过大,无法进行DP。
但是发现 \(n\) 的取值很小,可以从这里入手。
先考虑没有黑色格子的情况,很明显从 \((1,1)\) 走到 \((h,w)\) 的结果为
\(\dbinom{h - 1 + w - 1}{h - 1}\)。
现在加入黑色格子,考虑不合法的方案数,不难想到使用容斥原理,处理一个格子,两个格子等的情况,但是这是指数级的,也会超时。
只能考虑dp。设 \(f[i]\) 表示考虑前 \(i\) 个黑格子造成的不和法方案总数,不过由于重叠部分的这样会有后效性,应当改变状态设计。
思考一下,如果我们能把每一个点的不合法方案变得互不影响就可做了。
重新定义转移方程,将横(纵)坐标排好序后,设 \(f[i]\) 表示从 \((1,1)\) 点走到 \(i\) 号黑色格子,且中途不经过任何其他黑色格子的方案数。
同时将终点也视作第 \(n+1\) 个格子,则答案为 \(f[n+1]\)。
考虑转移,能走到第 \(i\) 个黑格子且不经过其他黑格子,根据容斥原理思想,可以看成所有方案减去不合法的方案,而不合法的状态可以分成两步统计:
-
第一步,到另一个在这个黑格子左上的黑格子,前面已经求得
-
第二步,计算另一个黑格子 \(j\) 到此黑格子的方案数,即为 \(\dbinom{x[i] - x[j] + y[i] - y[i]}{x[i] - x[j]}\)。
所以最后转移方程应为 \(\dbinom{h - 1 + w - 1}{h - 1} - \sum \dbinom{x[i] - x[j] + y[i] - y[i]}{x[i] - x[j]}\)(其中 \(j\) 满足 \(x[i] > x[j]\) 且 \(y[i]>y[j]\))。
而最后的答案即为 \(f[n+1]\) 的值。
Code
注意有除法取模,要在预处理阶乘时同时进行逆元操作。
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#define int long long
#define rep(_, __, ___) for(int _ = __; _ <= ___; _++)
#define per(_, __, ___) for(int _ = __; _ >= ___; _--)
using namespace std;
const int N = 2005;
const int H = 300005;
const int MOD = 1E9 + 7;
int h, w, n, f[N], frac[H] = {1}, frac_n[H] = {1};
struct node{
int x, y;
}a[N];
bool cmp(node xx, node yy)
{
if(xx.x == yy.x) return xx.y < yy.y;
else return xx.x < yy.x;
}
int ksm(int xx, int yy)
{
int res = 1;
while(yy > 0)
{
if(yy & 1)
{
res = res * xx % MOD;
}
xx = xx * xx % MOD;
yy /= 2;
}
return res;
}
signed main()
{
scanf("%lld%lld%lld", &h, &w, &n);
rep(i, 1, n)
{
scanf("%lld%lld", &a[i].x, &a[i].y);
}
rep(i, 1, 200000)
{
frac[i] = frac[i - 1] * i % MOD;
frac_n[i] = ksm(frac[i], MOD - 2);
}
sort(a + 1, a + n + 1, cmp);//排序,便于后面转移
n++;//n 应 +1
a[n].x = h, a[n].y = w; //别忘记把终点看作第 n + 1 个黑格子
rep(i, 1, n)
{
int t1 = a[i].x - 1;
int t2 = a[i].x - 1 + a[i].y - 1;
f[i] = ((frac[t2] * frac_n[t2 - t1]) % MOD * frac_n[t1]) % MOD; //计算方程前半段的组合数
//cout << i << " " << f[i] <<" " << frac[t2] << " " << frac_n[t1] << " " << frac_n[t2 - t1]<<endl;
rep(j, 1, i - 1)
{
if(a[i].x >= a[j].x && a[i].y >= a[j].y)//要判断一下
{
int temp = 0;
int temp1 = a[i].x - a[j].x;
int temp2 = a[i].x - a[j].x + a[i].y - a[j].y;
temp = ((frac[temp2] * frac_n[temp2 - temp1]) % MOD * frac_n[temp1]) % MOD;//处理后面减去不合法方案的组合数
f[i] = (f[i] - ((f[j] * temp) % MOD) + MOD) % MOD;//注意这里出现了减法,要处理
}
}
//cout << i << " " << f[i] <<endl;
}
printf("%lld", f[n]);
return 0;
}