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;
}
posted @ 2022-07-15 23:09  panjx  阅读(39)  评论(0编辑  收藏  举报