题意
有个很大的棋盘,然后棋子可以走马步(横走1竖走2或横走2竖走1)
然后给一个子从1,1走到n,m
只能往格子坐标增大的方向跳
棋盘中有一些障碍物
问方案数。
解题思路
如果棋子走1,1应该是都见过的情况
组合数学直接求C(n+m,n)即可
其实变成1,2也没区别,只是此时存在了不能走的情况
想想每次走一步对于两个方向的总贡献是3
因此至少两个格点的坐标差值dx,dy的和是3的倍数才行
而步数就是st=(dx+dy)/3
那么每个方向上走2格的次数就分别是
nx=x-st,和ny=y-st
因为不能走回头路,因此这两个数值要大于零
然后就又变成了组合数学问题
nx+ny步中选nx步横向走2
于是方案数就是C(nx+ny,nx)
由于数字很大,模的数比较小,这里用Lucas定理处理大组合数取模的问题
接下来考虑解决路径上的障碍物了
可行方案数=总方案数-路过了至少一个障碍的方案数
由于障碍物的数量有100之多
裸容斥显然是会爆炸的
但是由于棋子走是有方向性的
如从左下到右上可走
则只有更左下的点到更右上的点才有重复
则可以按照左下到右上的方向排序。
这里考虑dp的方法
dp[I]表示起始点到i障碍不过任何障碍的路径数
因此dp[i]只要算出起点到i点的所有路径数,再减掉前i-1个点自己的路径数*(i-1中某个点到I点路径数)
即可。
接下来路过至少一个障碍的方案数=
sigma(dp[i]*i点到n,m点的路径数)
为什么呢?想想dp[1]*1到终点路径数,算出了哪些?
过1点的所有路径。
dp[2]*2到终点路径数呢?不过1点过2点的所有路径数
dp[3]*3到终点路径数呢?不过1点不过2点过3点的所有路径数
因此整个计数过程完备且没有重复,即为所求。
至此问题得解
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int M=105;
const int MO=110119;
//以下lucas定理
LL qp(LL a,LL n,LL mo)
{
LL ans=1;
while(n)
{
if(n&1)
ans=(ans*a)%mo;
a=(a*a)%mo;
n>>=1;
}
return ans;
}
struct Lucas
{
LL p;
LL fa[MO],inv[MO];//阶乘和逆元,注意空间大小
void init(LL _p)
{
p=_p;
fa[0]=1;
for(int i=1;in)
return 0;
return fa[n]*inv[fa[n-m]*fa[m]%p]%p;
}
LL lucas(LL n,LL m)//大组合数取模
{
if(n==m||m==0)
return 1;
if(m>n)
return 0;
return c(n%p,m%p)*lucas(n/p,m/p)%p;
}
}lu;
struct Ob //障碍物
{
LL x,y;
bool operator<(const Ob &a)const
{
return x+y