HDU-5794-A Simple Chess-容斥加数学推导加大组合数

题意:要从(1,1)点走到(n,m)点去。只能走马字,而且只能向右下走。地图上还有r个障碍点,不能通过这些点。问走到终点有多少种路径。

思路:首先画图,观察这些点的形状,容易观察,得到马能走的点是一个变形的杨辉三角。我们可以通过坐标转换,将原来坐标的点转换到新的杨辉三角坐标中。

转换公式:

当然要先判断这个点是不是有效点(能不能被马走到)

if((x-1)*2<(y-1)||(y-1)*2<(x-1)||(x+y)%3!=2) 都是无效点

原来的点(x,y)

现在的点(xx,yy)计算方法 (其中我的xx从0开始的yy从1开始的)

t=(x+y)/3;

yy=y-t;

xx=t;

然后就是对一个杨辉三角型进行处理

每个点(包括障碍和终点)从原点(0,1)(转化过了。。)走到的当前点的路径数为C(n,m-1)

先对障碍点进行排序

然后我们记录一个ans数组  ans[i]表示从原点不通过其他障碍到第i个障碍的路径数

容易得到ans[i]=无障碍路径数-ans[j]*(j到i的路径数)  (j<i)

最后, 到达终点的种数为 无障碍路径数-ans[i]*(i到终点的路径)  i<=lr 

组合数比较大要用一个模版

注意如果有障碍在终点上,答案是0

#include<bits/stdc++.h>
#define LL long long
using namespace std;
struct NODE {
    long long x,y;
}c[111];
long long ans[111];

/**大组合数模版**/
const long long p = 110119;
long long PowMod (LL a,LL b,LL MOD){
    LL ret=1;
    while(b){
        if(b&1) ret=(ret*a)%MOD;
        a=(a*a)%MOD;
        b>>=1;
    }
    return ret;
}
long long fac[p+10];
void init () {
    fac[0]=1;
    for(int i=1;i<=p;i++)
        fac[i]=(fac[i-1]*i)%p;
}
long long C (LL n,LL m){
    if (m > n)
        return 0;
    LL ret=1;
    while(n&&m){
        LL a=n%p,b=m%p;
        if(a<b) return 0;
        ret=(ret*fac[a]*PowMod(fac[b]*fac[a-b]%p,p-2,p))%p;
        n/=p;
        m/=p;
    }
    return ret;
}
/**大组合数模版**/


bool cmp(const NODE A,const NODE B) {
    if(A.x==B.x) return A.y<B.y;
    return A.x<B.x;
}
bool pan(long long x,long long y) { ///判断这个点是不是有效点
    if((x-1)*2<(y-1)) return 0;
    if((y-1)*2<(x-1)) return 0;
    if((x+y)%3!=2) return 0;
    return 1;
}
int main()
{
    init();
    int kase=0;
    long long n,m;
    int r;
    while(scanf("%lld%lld%d",&n,&m,&r)!=EOF) {
        int lr=0;
        int fa=1;
        for(int i=1;i<=r;i++) {
            long long txx,tyy;
            scanf("%lld%lld",&txx,&tyy);
            if(txx==n&&tyy==m) {        ///如果有障碍在终点上
                fa=0;
            }
            if(pan(txx,tyy)&&(txx+tyy<n+m)) {       ///排除那些在终点后面的障碍
                lr++;
                c[lr].x=txx;
                c[lr].y=tyy;

            }
        }
        if(fa==0||!pan(n,m)) {
            printf("Case #%d: 0\n",++kase);
            continue;
        }
        /**点转化**/
        long long tt;
        for(int i=1;i<=lr;i++) {
            tt=(c[i].x+c[i].y)/3;
            c[i].y=c[i].y-tt;
            c[i].x=tt;
        }
        tt=(n+m)/3;
        m=m-tt;
        n=tt;

        sort(c+1,c+lr+1,cmp);///障碍点排序
        for(int i=1;i<=lr;i++) {
            ans[i]=C(c[i].x,c[i].y-1);
            for(int j=1;j<i;j++) {
                if(c[i].y<c[j].y) continue;
                if(c[i].x==c[j].x) continue;
                ans[i]=(ans[i]-ans[j]*C(c[i].x-c[j].x,c[i].y-c[j].y)%p+p)%p;///计算路径数
            }
        }
        long long tot=0;
        tot=C(n,m-1);
        for(int i=1;i<=lr;i++) {
            if(m<c[i].y) continue;
            tot=(tot-ans[i]*C(n-c[i].x,m-c[i].y)%p+p)%p;
        }
        printf("Case #%d: %lld\n",++kase,tot);
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值