HDU_5794_ASimpleChess(Lucas定理&&(容斥||dp))

A Simple Chess

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1336    Accepted Submission(s): 358


Problem Description
There is a n×m board, a chess want to go to the position
(n,m) from the position (1,1) .
The chess is able to go to position (x2,y2) from the position (x1,y1) , only and if only x1,y1,x2,y2 is satisfied that (x2x1)2+(y2y1)2=5, x2>x1, y2>y1 .
Unfortunately, there are some obstacles on the board. And the chess never can stay on the grid where has a obstacle.
I want you to tell me, There are how may ways the chess can achieve its goal.
 

Input
The input consists of multiple test cases.
For each test case:
The first line is three integers, n,m,r,(1n,m1018,0r100) , denoting the height of the board, the weight of the board, and the number of the obstacles on the board.
Then follow r lines, each lines have two integers, x,y(1xn,1ym) , denoting the position of the obstacles. please note there aren't never a obstacles at position (1,1) .
 

Output
For each test case,output a single line "Case #x: y", where x is the case number, starting from 1. And y is the answer after module 110119 .
 

Sample Input
  
  
1 1 0 3 3 0 4 4 1 2 1 4 4 1 3 2 7 10 2 1 2 7 1
 

Sample Output
  
  
Case #1: 1 Case #2: 0 Case #3: 2 Case #4: 1 Case #5: 5
 

Author
UESTC
 

Source
 

Recommend
wange2014


题意

有个很大的棋盘,然后棋子可以走马步(横走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 <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
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;i<p;i++)
        {
            fa[i]=fa[i-1]*i%p;
            //cout<<"i "<<i;
            inv[i]=qp(i,p-2,p);
            //cout<<"i "<<i;
        }
    }
    LL c(LL n,LL m)//小组合数取模
    {
        if(n==m||m==0)
            return 1;
        if(m>n)
            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<a.x+a.y;
    }
    bool operator==(const Ob &a)const
    {
        return a.x==x&&a.y==y;
    }
}ob[M];

bool check(LL dx,LL dy,LL &sx,LL &sy)//位置的差值,走的步数,返回能不能走
{
    if((dx+dy)%3!=0)
        return 0;
    LL step=(dx+dy)/3;
    sx=dx-step;//走2的数量
    sy=dy-step;//走1的数量
    if(sx<0||sy<0)
        return 0;
    return 1;
}

LL dp[M];
LL n,m;
int r;//障碍物数量
LL ans;

void solve()
{
    LL sx,sy;
    ans=0;
    memset(dp,0,sizeof(dp));
    if(check(n-1,m-1,sx,sy))
        ans=lu.lucas(sx+sy,sx);
    else
        return;
    for(int i=0;i<r;i++) //从开始到某障碍物不经过其他障碍物的路径数
        {
            if(!check(ob[i].x-1,ob[i].y-1,sx,sy))
                continue;
            dp[i]=lu.lucas(sx+sy,sx);
            for(int j=0;j<i;j++)
                if(check(ob[i].x-ob[j].x,ob[i].y-ob[j].y,sx,sy))
                    dp[i]=(dp[i]-dp[j]*lu.lucas(sx+sy,sx)%MO+MO)%MO;
        }
    for(int i=0;i<r;i++)
        if(check(n-ob[i].x,m-ob[i].y,sx,sy))
            ans=(ans-dp[i]*lu.lucas(sx+sy,sx)%MO+MO)%MO;
}

int main()
{
    lu.init(MO);
    int ca=1;
    //cout<<"!!"<<endl;
    while(scanf("%I64d%I64d%d",&n,&m,&r)!=EOF)
    {
        for(int i=0;i<r;i++)
            scanf("%I64d%I64d",&ob[i].x,&ob[i].y);
        sort(ob,ob+r);
        r=unique(ob,ob+r)-ob;
        solve();
        printf("Case #%d: %I64d\n",ca++,ans);
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值