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;
}