[BZOJ3782]上学路线

bzoj

description

小C所在的城市的道路构成了一个方形网格,它的西南角为((0,0)),东北角为((N,M))。小C家住在西南角,学校在东北角。现在有T个路口进行施工,小C不能通过这些路口。小C喜欢走最短的路径到达目的地,因此他每天上学时都只会向东或北行走;而小C又喜欢走不同的路径,因此他问你按照他走最短路径的规则,他可以选择的不同的上学路线有多少条。由于答案可能很大,所以小C只需要让你求出路径数(mod P)的值。

Input

第一行,四个整数(N,M,T,P)
接下来的(T)行,每行两个整数,表示施工的路口的坐标。

Output

一行,一个整数,路径数(mod P)的值。

Sample Input

3 4 3 1019663265
3 0
1 1
2 2

Sample Output

8

HINT

(1 le N,M le 10^{10})

(0 le T le 200)

(p=1000003)(p=1019663265)

sol

容斥,方法同bzoj4767 两双手,就是对于每个位置,枚举不合法的情况经过的第一个不合法点,复杂度(O(T^2))
发现这里的组合数比较大,所以就卢卡斯一波。
(10^6+3)是一个质数,可以直接卢卡斯。
(1019663265=3 imes5 imes6793 imes10007),所以还得套个(CRT)合并。

code

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
const int N = 1e6+5;
struct node{
	ll x,y;
	bool operator < (const node &b) const{
		return x==b.x?y<b.y:x<b.x;
	}
}a[205];
int p[5]={1000003,3,5,6793,10007};
int k,mod,inv[5][N],jc[5][N],jcn[5][N],f[205];
ll n,m;
int C(ll n,ll m,int op){
	if (n<m) return 0;
	if (n<p[op]&&m<p[op]) return 1ll*jc[op][n]*jcn[op][m]%p[op]*jcn[op][n-m]%p[op];
	return 1ll*C(n/p[op],m/p[op],op)*C(n%p[op],m%p[op],op)%p[op];
}
int cal(ll x,ll y){
	if (x<0||y<0) return 0;
	if (mod==p[0]) return C(x+y,y,0);
	int res=0;
	for (int i=1;i<=4;++i){
		int tmp=C(x+y,y,i),Inv=inv[i][mod/p[i]%p[i]];
		res=(res+1ll*Inv*(mod/p[i])%mod*tmp%mod)%mod;
	}
	return res;
}
int main(){
	for (int i=0;i<=4;++i){
		inv[i][0]=inv[i][1]=jc[i][0]=jcn[i][0]=1;
		for (int j=2;j<p[i];++j) inv[i][j]=1ll*inv[i][p[i]%j]*(p[i]-p[i]/j)%p[i];
		for (int j=1;j<p[i];++j) jc[i][j]=1ll*jc[i][j-1]*j%p[i],jcn[i][j]=1ll*jcn[i][j-1]*inv[i][j]%p[i];
	}
	scanf("%lld%lld%d%d",&n,&m,&k,&mod);
	for (int i=1;i<=k;++i) scanf("%lld%lld",&a[i].x,&a[i].y);
	a[++k]=(node){n,m};sort(a+1,a+k+1);
	for (int i=1;i<=k;++i){
		f[i]=cal(a[i].x,a[i].y);
		for (int j=1;j<i;++j) f[i]=(f[i]-1ll*f[j]*cal(a[i].x-a[j].x,a[i].y-a[j].y)%mod+mod)%mod;
	}
	printf("%d
",f[k]);return 0;
}

原文地址:https://www.cnblogs.com/zhoushuyu/p/9311113.html