HDU5794 A Simple Chess (容斥+卢卡斯)

37 篇文章 0 订阅

题意:

一匹”马”在棋盘上 (1,1) 的位置,每次跳跃时横纵坐标都必须增大.棋盘上还有 K 个障碍物(保证不在 (1,1) 处).求跳到 (n,m) 的方案数, .

题解:

马的日字步不好处理,需要先通过一种略奇妙的坐标变换,把问题转换成一步只能向右和向下.如果下标都改为从 1 开始(即读入的下标减 1 ),那么很容易推得变换后的坐标为 x=13x+23y,y=23x13y. 如果变换后的坐标不是非负整数,就说明这个点原本就无法到达.

这样转化之后的好处是如果没有障碍物的限制,从一个矩形左上角到达右下角的方案数可以用组合数方便地计算出来.所以我们考虑用总方案数减去经过至少一个障碍物的方案数.

为了避免重复计算,可以枚举第一个经过的障碍物,这样又转化为求从起点到这个障碍物,途中不经过其他障碍物的方案数以及从这个障碍物没有限制地到达终点的方案数.后者依旧用组合数,而前者是和原问题相同的一个子问题,又可以用同样的方法求.

于是可以设计一个dp算法, dp[i] 就表示从起点到障碍物 i ,途中不经过其他障碍物的方案数.这样dp的复杂度是 O(K2) 的.

当然,求组合数需要用到Lucas定理.

于是总的复杂度为 O(K2logP(n+m)) .


#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define L(i) i<<1
#define R(i) i<<1|1
#define INF  0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-9
#define maxn 10010
#define MOD 1000000007

struct point
{
    long long x,y;
    bool operator <(const point &a)const
    {
        return x < a.x || (x == a.x && y < a.y);
    }
} a[210];
long long n,m;
int r;
long long dp[110];
long long fac[1000010];


long long PowMod(long long a,long long b,long long mod)
{
    long long ret = 1;
    while(b)
    {
        if(b & 1)
            ret = (ret * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return ret;
}
long long Get_Fact(long long p)
{
    fac[0] = 1;
    for(long long i = 1; i <= p; i++)
        fac[i] = (fac[i-1] * i) % p;
}
long long Lucas(long long n,long long m,long long p)
{
    long long ret = 1;
    while(n && m)
    {
        long long a = n % p;
        long long 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;
}
long long solve(int i,int j,long long mod)
{
    long long x1 = a[i].x;
    long long y1 = a[i].y;
    long long x2 = a[j].x;
    long long y2 = a[j].y;
    long long c1 = (2*x2-y2-2*x1+y1) / 3;
    long long c2 = (2*y2-x2-2*y1+x1) / 3;
    if(2*x2-y2-2*x1+y1 < 0 || 2*y2-x2-2*y1+x1 < 0 || (2*x2-y2-2*x1+y1)%3 || (2*y2-x2-2*y1+x1)%3)
        return 0;
    return Lucas(c2+c1,c1,mod);
}
int ok(int i,int j)
{
    if(a[i].x <= a[j].x && a[i].y <= a[j].y)
        return 1;
    return 0;
}
int main()
{
    int t,C = 1;
    //scanf("%d",&t);
    long long mod = 110119;
    Get_Fact(mod);
    while(scanf("%lld%lld%d",&n,&m,&r) != EOF)
    {
        for(int i = 1; i <= r; i++)
            scanf("%lld%lld",&a[i].x,&a[i].y);
        a[0].x = 1;
        a[0].y = 1;
        a[r+1].x = n;
        a[r+1].y = m;
        sort(a,a+r+2);
        memset(dp,0,sizeof(dp));
        for(int i = 1; i <= r+1; i++)
        {
            dp[i] = solve(0,i,mod);
            for(int j = i-1; j > 0; j--)
                if(ok(j,i))
                    dp[i] = (dp[i] - dp[j]*solve(j,i,mod)%mod + mod)%mod;
        }
        printf("Case #%d: %lld\n",C++,dp[r+1]);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值