Description
你在二维平面上行走,要从(0,0)走到(n,m)
你有三种走法,假设你当前在(x,y),你可以走到(x+1,y+1),(x+1,y-1)或者(x+2,y)
求方案数模100003
n,m<=1e18
Solution
将坐标系逆时针旋转45度
原来的问题就变成
在二维平面上行走,要从
(0,0)
走到
(n+m2,n−m2)
你有三种走法,假设你当前在(x,y),你可以走到(x,y+1),(x+1,y)或者(x+1,y+1)(向上,向右或者向右上方斜着走)
求方案数
设
A=n+m2,B=n−m2
显然若n,m奇偶性不同直接输出0,
n<m
也输出0
比较正常的思路是枚举斜着走了i步
那么总共走了A+B-i步
因为
A>B
,所以答案就是
∑i=0ACiACAA+B−i
(总共向右了A次,其中i次是斜着的,总共走了A+B-i步,其中A步是有向右的)
把两个A换成B也是一样的
又根据lucas定理,在模数进制下每一位对应组合数再乘起来
这样就可以数位DP了(虽然只有三四位的样子。。。),注意还要多一维0/1表示减法是否退位
还有一种非常棒的结论
我们考虑将一步斜向右上看做先向右再向上两步的一个合并
那么向右走与向上走的匹配数是 CiACiB
此时的i相当于枚举路径中有多少个从右到上的转折
然后一个转折可以变成斜着的,也可以不变成斜着的,因此乘上
2i
我们发现现在路径就是唯一确定了,因为向右和向上必定是连续的段,否则就会产生新的转折。
那么总答案就是 ∑i=0B2iCiACiB
此时用lucas定理,就不需要数位DP了,同时发现 2i 我们将它也表示成模数进制,根据费马小定理,质数对模数-1取模,我们发现刚好就是它的在模数进制下每一位
直接对每一位计算,最后乘起来就行了
这个数有一个学名叫Delannoy Number
参见Wikipedia - Delannoy number
Code
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define LL long long
#define mo 100003
#define M 2005
#define N 400005
using namespace std;
LL n,m,js[N],ny[N],l,r;
int f[M][2*M];
LL ksm(LL k,LL n)
{
LL s=1;
for(;n;k=k*k%mo,n>>=1) if(n&1) s=s*k%mo;
return s;
}
LL C(LL n,LL m)
{
if(n<m) return 0;
if(n>=mo) return C(n%mo,m%mo)*C(n/mo,m/mo)%mo;
return js[n]*ny[m]%mo*ny[n-m]%mo;
}
int main()
{
cin>>n>>m;
if((n+m)%2!=0||n<m) printf("0\n");
else if(n==m) printf("1\n");
else
{
js[0]=1;
LL ans=1;
fo(i,1,mo-1) js[i]=js[i-1]*(LL)i%mo;
ny[mo-1]=ksm(js[mo-1],mo-2);
fod(i,mo-2,0) ny[i]=ny[i+1]*(LL)(i+1)%mo;
l=(n-m)/2,r=(n+m)/2;
while(l>0)
{
LL l1=l%mo,r1=r%mo,s=0;
l/=mo,r/=mo;
fo(i,0,min(l1,r1)) (s+=C(l1,i)*C(r1,i)%mo*ksm(2,i))%=mo;
ans=ans*s%mo;
}
printf("%lld\n",ans);
}
}