[AtCoder Educational DP Contest] Y - Grid 2(容斥 + dp)

20 篇文章 0 订阅
9 篇文章 1 订阅

problem

luogu

给一个 H × W H\times W H×W 的网格,每一步只能向右或向下走,给出一些坐标,这些坐标对应的位置不能经过,求从左上角 ( 1 , 1 ) (1,1) (1,1) 走到右下角 ( H , W ) (H,W) (H,W) 的方案数,答案对 1 0 9 + 7 10^9+7 109+7 取模。

1 ≤ H , W ≤ 1 e 5 , 1 ≤ n ≤ 3000 1\le H,W\le 1e5,1\le n\le 3000 1H,W1e5,1n3000

solution

f ( i , j ) : f(i,j): f(i,j): 在位置 ( i , j ) (i,j) (i,j) 的方案数显然已经不行了。

关注到障碍物的数量非常少,考虑从障碍物的角度入手。

这种在网格图上只有右边和下边的走法抽象成数学模型就是组合数。

( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 的方案数即为 ( x 2 − x 1 + y 2 − y 1 x 2 − x 1 ) \binom{x_2-x_1+y_2-y_1}{x_2-x_1} (x2x1x2x1+y2y1)

这种方案数包含所有经过不同的若干个障碍物的情况。

我们考虑容斥,至少经过 0 0 0 个障碍物 − - 至少经过 1 1 1 个障碍物 + + + 至少经过两个障碍物 … \dots

这是普通的容斥,是总集合靠着子集加加减减来求得。

S = A 1 + ( A 2 − A 1 ⋂ A 2 ) + ( A 3 − ( A 1 ⋃ A 2 ) ⋂ A 3 ) + . . . + ( A n − ( A 1 ⋃ ⋯ ⋃ A n − 1 ) ⋂ A n ) S=A_1+(A_2-A_1\bigcap A_2)+(A_3-(A_1\bigcup A_2)\bigcap A_3)+...+(A_n-(A_1\bigcup\dots\bigcup A_{n-1})\bigcap A_n) S=A1+(A2A1A2)+(A3(A1A2)A3)+...+(An(A1An1)An)

每次从剩余元素中,把属于 A i A_i Ai 的取走,直到取完总集合。

f ( i ) : f(i): f(i): 只经过第 i i i 个障碍物的方案数。

将障碍物按 x x x 坐标升序排列,相同则按 y y y 升序。

把最后终点也当作障碍物即可。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 200005
#define int long long 
#define mod 1000000007
int n, r, c;
int f[maxn], fac[maxn], inv[maxn];
struct node { int x, y; }p[maxn];
int qkpow( int x, int y ) {
	int ans = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}
int C( int n, int m ) {
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
void init( int n = 2e5 ) {
	fac[0] = inv[0] = 1;
	for( int i = 1;i <= n;i ++ ) fac[i] = fac[i - 1] * i % mod;
	inv[n] = qkpow( fac[n], mod - 2 );
	for( int i = n - 1;i;i -- ) inv[i] = inv[i + 1] * (i + 1) % mod;
}
signed main() {
	scanf( "%lld %lld %lld", &r, &c, &n ); init();
	for( int i = 1;i <= n;i ++ ) scanf( "%d %d", &p[i].x, &p[i].y );
	p[++ n] = (node){ r, c };
	sort( p + 1, p + n + 1, []( node a, node b ){ return a.x == b.x ? a.y < b.y : a.x < b.x; } );
	for( int i = 1;i <= n;i ++ ) f[i] = C( p[i].x + p[i].y - 2, p[i].x - 1 );
	for( int i = 1;i <= n;i ++ )
		for( int j = i + 1;j <= n;j ++ )
			if( p[i].y <= p[j].y )
				( f[j] -= f[i] * C(p[j].x - p[i].x + p[j].y - p[i].y, p[j].x - p[i].x) ) %= mod;
	printf( "%lld\n", (f[n] + mod) % mod );
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值