Codeforces Gym 100379J Move the (p, q)-knight to the corner!(DP+lucas定理)

题意:现在有一个棋盘,要从(1,1)处走到(n,m)处,中间有k个坏点不能走,每次只能走(x+p y+q)或者(x+q, y+p)的位置,问有多少种走法。

思路:这道题是 Codeforces Gym 100589F Count Ways(DP+组合数学) 的强化版。

我们要想办法将这道题转化为这个简化版,考虑这样一个问题,如果从(1,1)走到某一个位置(x,y)那么一定没有或有且仅有一种走法,因为这相当于求一个二元一次方程,也就是说我们把现在点的坐标(x,y)转化为从(1,1)走到位置(x,y)需要竖走几步u和横走几步v,也就是说新的坐标是(u,v)。

因为我们只用到了坏点的坐标和终点的坐标所以我们只需要转换这些点的坐标即可。

注意这道题有两个细节,

一是求解二元一次方程时注意求出来的u,v可能是负的这种情况也是不合题意的(之前一直wa在这里),

二是转换后的坐标不遵从原来的坐标排序关系,因为转换坐标后之前x小的转换后可能u反而大了,所以dp的顺序可能会乱,所以要记录每个坏点之前的序号。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<set>
#include<ctime>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

//const int MAXN = 5000000 + 5;
//const int INF = 0x3f3f3f3f;
LL n, m, p, q, k, cnt;
LL MOD;
LL jc[1001000], inv[1001000];
struct Node {
	LL x, y, id;
	bool operator < (const Node& A) const {
		if(x == A.x) return y < A.y;
		return x < A.x;
	}
} node[20];
LL dp[20];
LL pow_mod(LL a, LL p, LL n) {
	if(p == 0) return 1;
	LL ans = pow_mod(a, p/2, n);
	ans = ans * ans % n;
	if(p%2 == 1) ans = ans * a % n;
	return ans;
} 
void init() {
	inv[0] = jc[0] = 1;
	for(int i = 1; i <= 1000010; i++) jc[i] = jc[i-1]*i % MOD;
	for(int i = 1; i <= 1000010; i++) inv[i] = inv[i-1]*pow_mod(i, MOD-2, MOD) % MOD;
} 
LL c(LL n, LL m) {
	return jc[n] * inv[m] % MOD * inv[n-m] % MOD;
}
LL C(int n, int m) {
	LL ret = 1;
	while(n>0 && m>0) {
		if(n%MOD < m%MOD) return 0;
		ret = ret * c(n%MOD, m%MOD) % MOD;
		n /= MOD, m /= MOD; 
	}
	return ret;
}
int main() {
    //freopen("input.txt", "r", stdin);
	scanf("%I64d%I64d%I64d%I64d%I64d%I64d", &p, &q, &MOD, &n, &m, &k);
	init();
	for(int i = 1; i <= k; i++) scanf("%d%d", &node[i].x, &node[i].y);
	node[k+1].x = n, node[k+1].y = m;
	cnt = 0;
	for(int i = 1; i <= k+1; i++) {
		bool tag = 0;
		LL ty = ((LL)(node[i].x-1)*p-(LL)(node[i].y-1)*q)/((LL)p*p-(LL)q*q);
		LL tx = ((LL)(node[i].x-1)*q-(LL)(node[i].y-1)*p)/((LL)q*q-(LL)p*p);
		if(tx>=0 && ty>=0 && tx*p+ty*q==node[i].y-1 && ty*p+tx*q==node[i].x-1) {
			node[i].x = tx;
			node[i].y = ty;
			node[i].id = ++cnt;
			node[cnt] = node[i];
			tag = 1;
		}
		if(i==k+1 && !tag) {
			puts("0");
			return 0;
		}
	}
	sort(node+1, node+cnt+1);
	for(int i = 1; i <= cnt; i++) {
		for(int j = 1; j < i; j++) {
			if(node[j].y <= node[i].y)
				dp[i] = (dp[i]-dp[j]*C(node[i].y-node[j].y+node[i].x-node[j].x, node[i].x-node[j].x)) % MOD;
		}
		dp[i] = (dp[i]+C(node[i].y+node[i].x, node[i].x)+MOD) % MOD;
		if(node[i].id == cnt) cout << dp[i] << endl;
	}  
	return 0;
}


















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值