Codeforces Gym 100379J Move the (p, q)-knight to the corner! 组合数学DP, Lucas定理

题目大意:

就是现在一个n*m的棋盘上有一个位于顶点的马(1, 1), 现在要从(1, 1)移动到(n, m), 中间有k个位置是不能走的 k <= 10

并且每次走的时候必须遵循马走的规则: 从(x , y)走到(x + p, y + q)或者 (x + q, y + p)

n, m <= 1e9, k <= 10, p, q <= 20问从起点走到终点必经过坏点的路线有多少种, 结果对d取模 (d <= 1e6)


大致思路:

首先考虑每一个位置(x, y)到达这个位置需要的两种走法的次数分别是t1, t2, 那么 t1*p + t2*q == y, t1*q + t2*p == x

也就是说每一个位置(x, y)都可以转换成坐标(tmpx = 1, tmpy + 1)其中 tmpx = abs(ty*p - tx*q) / abs(p*p - q*q), tmpy = abs(ty*q - p*tx) / abs(q*q - p*p);

也就是将元问题转换成从左上角(1, 1)走到右下角(new_n, new_m)的方案数, 每次向右或者向下走一步(x + 1, y)或(x, y + 1)不经过换点到终点的步数

于是这个DP就和Codeforces 559C一样了, 需要注意的是处理组合数不可能将所有的阶乘以及逆元预处理出来

而组合数C[x][y]中x, y <= 2e9于是需要用到Lucas定理, 因为d <= 1e6于是就处理1e6以内的阶乘就可以了

Lucas定理: Lucas(x, y, mod) = C(x % mod, y % mod)*Lucas(x / mod, y / mod, mod) % mod


代码如下:

Result  :  Accepted     Memory  :  7826 ms     Time  :  62 ms

/*
 * Author: Gatevin
 * Created Time:  2015/8/14 13:24:30
 * File Name: Sakura_Chiyo.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

struct Point
{
    lint x, y;
    int id;
    Point(){}
    Point(lint _x, lint _y, int _id)
    {
        x = _x;
        y = _y;
        id = _id;
    }
};

bool cmp(Point p1, Point p2)
{
    return p1.x < p2.x || (p1.x == p2.x && p1.y < p2.y);
}

Point P[20];

lint p, q, mod;
lint n, m;
int k;
lint dp[100];
lint fac[1000010];

lint quick(lint base, lint pow)
{
    lint ret = 1;
    while(pow)
    {
        if(pow & 1) ret = ret*base % mod;
        pow >>= 1;
        base = base*base % mod;
    }
    return ret;
}

lint get(lint x, lint y)//C[x][y], x, y <= 1e6
{
    return fac[x]*quick(fac[y]*fac[x - y] % mod, mod - 2);
}

//Lucas(x, y, mod) = C(x % mod, y % mod)*Lucas(x/mod, y/mod, mod) % mod
lint C(lint x, lint y)//C[x][y], x, y <= 1e9
{
    lint ret = 1;
    while(x > 0 && y > 0)
    {
        if(x % mod < y % mod) return 0;
        ret = ret*get(x % mod, y % mod) % mod;
        x /= mod, y /= mod;
    }
    return ret;
}

int main()
{
    while(~scanf("%I64d %I64d %I64d", &p, &q, &mod))
    {
        fac[0] = 1;
        for(int i = 1; i <= mod; i++) fac[i] = fac[i - 1]*i % mod;
        
        scanf("%I64d %I64d", &n, &m);
        P[0].x = P[0].y = 1;
        P[0].id = 0;
        int cnt = 0;
        scanf("%d", &k);
        for(int i = 1; i <= k; i++)
        {
            lint tx, ty;
            scanf("%I64d %I64d", &tx, &ty);
            tx--, ty--;
            lint tmpx = abs(ty*p - tx*q) / abs(p*p - q*q), tmpy = abs(ty*q - p*tx) / abs(q*q - p*p);
            if(tmpx*p + tmpy*q == ty && tmpx*q + tmpy*p == tx)
            {
                ++cnt;
                P[cnt] = Point(tmpx + 1, tmpy + 1, cnt);
            }
        }
        n--, m--;
        lint tmpx = abs(m*p - n*q) / abs(p*p - q*q), tmpy = abs(m*q - p*n) / abs(q*q - p*p);
        if(tmpx*p + tmpy*q != m || tmpx *q + tmpy*p != n)
        {
            puts("0");
            continue;
        }
        ++cnt;
        P[cnt] = Point(abs(m*p - n*q) / abs(p*p - q*q) + 1, abs(m*q - p*n) / abs(q*q - p*p) + 1, cnt);
        sort(P, P + cnt + 1, cmp);
        dp[0] = 1;
        for(int i = 1; i <= cnt; i++)
        {
            dp[i] = C(P[i].x - P[0].x + P[i].y - P[0].y, P[i].x - P[0].x);
            for(int j = 1; j < i; j++)
                if(P[j].y <= P[i].y)
                    dp[i] = (dp[i] - dp[j]*C(P[i].x - P[j].x + P[i].y - P[j].y, P[i].x - P[j].x) % mod + mod) % mod;
            if(P[i].id == cnt)
            {
                printf("%I64d\n", dp[i]);
                break;
            }
        }
    }
    return 0;
}
/*
1 2 997
10 10
3
5 9
6 2
3 2
*/




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值