HDU 5794 A Simple Chess 16多校6 dp 容斥 lucas

5 篇文章 0 订阅
3 篇文章 0 订阅

题意:给一个二维直角坐标系下的点(n,m)(1<=n,m<=10^18)和另外r(0<=r<=100)个点。一个棋子从(1,1)点走向(n,m),走法规定如下,设当前点为(x1,y1),棋子可以走到(x2,y2)当且仅当(x2-x1)^2+(y2-y1)^2==5且x1<x2,y1<y2。
问棋子不经过障碍点走到点(n,m)的方案数。

思路:
对于我这个蒟蒻,觉得这是一个好题。比赛的时候一个小时我们队就做出了1001和1003,接下来挂机4个小时…………(满脸黑线)
知道了思路之后其实很简单,r最大只有100,应该从这里入手,设dp[i]的含义为: 不经过其他障碍点,从(1,1)点走到i点的方案数。则dp[i]可初始化为((1,1)点到第i个点的方案数),状态转移方程dp[i]=dp[i]-(第j个点到第i个点的方案数)*dp[j],其中j 是在i之前的点。(就是把r个点按x,y排序,一个O(r^2)的二重循环)。
某点走到某点的方案数就是一个组合数,不理解的话,画图模拟下就可以很快理解。
这题还有一个问题就是数据范围太大,组合数很难计算,可以使用Lucas定理(详见代码),此法在O(mod)的预处理后,有着(以mod为底的)log级别的速度,很厉害。之前没有接触过,这次遇到了也算有收获。

自己在读入点时就去掉了很多不符合要求的点,在对点排序后,把(n,m)点放在了node数组的后面,两个for枚举点的时候,也用二维坐标系的一些知识,去掉了不符合要求的点。详见代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define lowbit(x) (x&(-x))
typedef long long LL;
inline void fre1(){freopen("input.txt","r",stdin);/*freopen("output.txt","w",stdout);*/}
inline void fre2(){fclose(stdin);/*fclose(stdout);*/}
const int MAXN=100+50;
const double EPS=1e-8;
const int MOD=110119;
struct Node{
    LL x,y;
    bool operator <(const Node& rhs) const{
        return x<rhs.x||(x==rhs.x&&y<rhs.y);
    }
}node[MAXN];
LL dp[MAXN],fac[MOD+5];

LL powmod(LL a,LL b){
    LL ret=1;
    for(;b;b>>=1){
        if(b&1) ret=(ret*a)%MOD;
        a=(a*a)%MOD;
    }
    return ret;
}
LL Lucas(LL n,LL m){
    LL ret=1;
    while(n&&m){
        LL a=n%MOD,b=m%MOD;
        if(a<b) return 0;
        ret=(ret*fac[a]*powmod(fac[b]*fac[a-b]%MOD,MOD-2))%MOD;
        n/=MOD;m/=MOD;
    }
    return ret;
}

int main()
{
    fac[0]=1;
    for(int i=1;i<=MOD;++i) fac[i]=(fac[i-1]*i)%MOD;
    LL n,m,x,y;
    int kase=0,r;
    while(~scanf("%I64d%I64d%d",&n,&m,&r)){
        int tot=0;
        bool flag=false;
        if((n+m)%3!=2) flag=true;
        if(m>2*n-1) flag=true;
        if(2*m<n+1) flag=true;
        for(int i=0;i<r;++i){
            scanf("%I64d%I64d",&x,&y);
            if(x==n&&y==m) flag=true;
            if(flag) continue;
            if((x+y)%3!=2) continue;
            if(y>2*x-1) continue;
            if(2*y<x+1) continue;
            node[tot++].x=x;
            node[tot-1].y=y;
        }
        if(flag){
            printf("Case #%d: 0\n",++kase);
            continue;
        }
        sort(node,node+tot);
        node[tot].x=n;node[tot].y=m;
        for(int i=0;i<=tot;++i){
            x=node[i].x-1,y=node[i].y-1;
            LL a=(x+y)/3;
            LL b=(abs(x-a)+abs(y-2*a))/2;
            dp[i]=Lucas(a,b);
            for(int j=0;j<i;++j){
                if(2*(node[j].y-node[i].y)>node[j].x-node[i].x) continue;
                if(node[j].y-node[i].y<2*(node[j].x-node[i].x)) continue;
                x=node[i].x-node[j].x,y=node[i].y-node[j].y;
                a=(x+y)/3;
                b=(abs(x-a)+abs(y-2*a))/2;
                dp[i]-=(Lucas(a,b)*dp[j])%MOD;
                while(dp[i]<0) dp[i]+=MOD;
            }
        }
        printf("Case #%d: %I64d\n",++kase,dp[tot]);
    }
    return 0;
}

其他:
这题看了看就有容斥的想法,但因为平时自己接触的容斥题实在是太少了,所以多校的确是一个锻炼的好机会啊。
同时,多校第一场的Rigid Frameworks,也是用到了容斥思想,而且也和这题有点类似,不断依靠之前的状态来更新自己的状态。自己写过这题: http://blog.csdn.net/dpppbr/article/details/51972196
还有多校第似场的Lucky7,用状压进行容斥,因为此题中,每个p值a值并没有关联性,所以只能用暴力枚举的方法来计算答案,如果容斥之间的各点有关系的话,计算量就可以简化很多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值