LOJ6614 THUPC 2019 过河卒二(chess)

LOJ6614 THUPC 2019 过河卒二(chess)

题目链接

https://loj.ac/problem/6614

题目大意

给你一个 $n$ 行 $m$ 列的棋盘,从 $(1, 1)$ 走到 $(n, m)$ ,有 $k$ 个点不能经过,每次可以向上向右或者向斜上方走。问有多少走法可以走出棋盘。结果对 $59393$ 取模。

题目分析

由于所有的路径均需走出棋盘,那么显然只会走到 $n + 1$ 行或者 $m + 1$ 列,那么显然如果不允许他们继续走向其他的行或列,那么显然走出去后都有唯一的路径走到 $(n + 1, m + 1)$ 点。也就是说,题目便转化为从 $(1, 1)$ 走到 $(n + 1, m + 1)$ 。

此时再考虑从$(0, 0)$ 走到 $(x, y)$ ,显然需要走 $x$ 步向上, $y$ 步向右,由于还有可能斜着走,注意到 $m$ 是 $10 ^ 5$ 量级的。因此可以枚举斜着走的次数,那么可以获得如下表达式:

$$\sum\limits_{i=0}^{\min(x, y)}{C_{x + y – 2i}^{x – i} \cdot C_{x + y – i}^{i}}$$

注意到 $n$ 是 $10 ^ 9$ 量级的,而模数是 $59393$,因此需要使用 $Lucas$ 定理来求组合数。

之后考虑不经过 $k$ 个点,可以使用容斥原理解决。

不过 $Lucida$ 跟我讲了另一种做法,统计第一个经过的点是谁的方法数即可,这样显然也是补充不漏的,求解速度还会更快一点。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 5;

long long mod = 59393;

int n, m, k, t;

struct point {
	long long x, y;
} p[25];

long long fac[maxn];
long long inv[maxn];

long long f[25][25];
long long g[25][2];
long long h[25];

int read(){
	int x;
	scanf("%d", &x);
	return x;
}

bool cmp(point x, point y){
	if(x.x == y.x)return x.y < y.y;
	return x.x < y.x;
}

long long qpow(long long b, long long x){
	long long ret = 1;
	
	while(x){
		if(x & 1){
			ret = ret * b % mod;
		}
		b = b * b % mod;
		x >>= 1;
	}
	
	return ret;
}

long long getc(long long x, long long y){
	if(x < y)return 0;
	return fac[x] * inv[x - y] % mod * inv[y] % mod;
}

long long lucas(long long x, long long y){
	if(y == 0)return 1;
	return getc(x % mod, y % mod) * lucas(x / mod, y / mod) % mod;
}

int init(){
	fac[0] = 1;
	inv[0] = 1;
	for(long long i=1;i<mod;i++){
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = qpow(fac[i], mod - 2);
	}
	return 0;
}

int main(){
	int i, j;
	long long l, r;
	long long x, y;
	long long ans = 0;
	
	init();
	
	n = read();
	m = read();
	k = read();
	
	for(i=0;i<k;i++){
		p[i].x = read();
		p[i].y = read();
	}
	
	sort(p, p + k, cmp);
	
	for(i=0;i<k;i++){
		for(j=i+1;j<k;j++){
			if(p[i].y > p[j].y)continue;
			l = p[j].x - p[i].x;
			r = p[j].y - p[i].y;
			for(x=0;x<=min(l,r);x++){
				f[i][j] = (f[i][j] + lucas(l + r - 2 * x, l - x) * lucas(l + r - x, x) % mod) % mod;
			}
		}
	}
	
	for(i=0;i<k;i++){
		l = p[i].x - 1;
		r = p[i].y - 1;
		for(x=0;x<=min(l,r);x++){
			g[i][0] = (g[i][0] + lucas(l + r - 2 * x, l - x) * lucas(l + r - x, x) % mod) % mod;
		}
		
		l = n - p[i].x + 1;
		r = m - p[i].y + 1;
		for(x=0;x<=min(l,r);x++){
			g[i][1] = (g[i][1] + lucas(l + r - 2 * x, l - x) * lucas(l + r - x, x) % mod) % mod;
		}
	}
	
	l = n;
	r = m;
	for(x=0;x<=min(l,r);x++){
		ans = (ans + lucas(l + r - 2 * x, l - x) * lucas(l + r - x, x) % mod) % mod;
	}
	
	/*第二种做法*/
	for(i=0;i<k;i++){
		h[i] = g[i][0];
		for(j=0;j<k;j++){
			h[i] -= h[j] * f[j][i] % mod;
			h[i] %= mod;
		}
		ans -= h[i] * g[i][1] % mod;
		ans %= mod;
	}
	
	/*容斥做法*/
	/*for(i=1;i<(1<<k);i++){
		x = -1;
		long long tmp = 1;
		int cnt = 0;
		for(j=0;j<k;j++){
			if((i >> j) & 1){
				cnt++;
				if(x == -1){
					tmp = (tmp * g[j][0]) % mod;
				}else{
					tmp = (tmp * f[x][j]) % mod;
				}
				x = j;
			}
		}
		
		tmp = (tmp * g[x][1]) % mod;
		ans += (cnt % 2 == 0 ? 1ll : -1ll) * tmp;
		ans %= mod;
	}*/

	ans = (ans % mod + mod) % mod;
	
	printf("%lld\n", ans);
	
	return 0;
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据