运行要求
运行时间限制: 2sec
内存限制: 1024MB
原文链接

题目
二次元的坐标系的原点坐标(0,0)上有一名骑士,骑士如果处在格子(i,j)上的话可以按照如下的方式移动
(i+1,j+2)
(i+2,j+1)
如果骑士想到达(X,Y)的话,有多少种移动方案。

输入前提条件

  • 1<=X<=10^6
  • 1<=X<=10^6
  • 输入的值均为整数

输入
输入都以以下标准从命令行输入

X Y

输出
求骑士从原点(0,0)到(X,Y)的移动方法的总数,结果取10^9+7的余数


例1
输入

3 3

输出

2

(0,0)->(1,2)->(3,3)
(0,0)->(2,1)->(3,3)
两种方法

例2
输入

2 2

输出

0

骑士不能移动到(2,2)的坐标

例3
输入

999999 999999

输出

151840682

最后的结果是和10^9+7相除的余数


读懂题目
这题可以抽象成坐标系的题目
Atcoder Context 125D.001.jpeg

解题思路
1.首先有下面两种走法,红色的长方形和蓝色的长方形

Atcoder Context 125D.002.jpeg
我们设红色的长方形的数量是m,蓝色的长方形的数量是n。那么m和n满足以下条件

2*m + n == X
2*n + m == Y

我们从0开始遍历m,可以找到所有满足条件的m和n的组合
遍历的条件是以下

2*m <= X
m <= Y

另外X+Y = 3m + 3n = 3*(M+N)
说明X+Y必须为3的倍数,不然的话通过红色或者蓝色长方形是到达不了(X,Y)的

就这样我们找到了可能的m,n,题目要求的是所有移动方法的数量。这里我们可以抽象成以下的问题

2.有m个大小一样的红球,n个一样的蓝球。我们任意排列这些球,请问有多少种排法?

这一高中数学排列组合的一道经典题目
答案是C(m+n,n)

证明大概是如下
我们把所有的排列组合计算一下是(m+n)的阶乘
这些组合里面,因为蓝色的球都是一样的球和红色的球的也都是一样的球。
所以
所有的排列组合(m+n)的阶乘 = 红色的球的排列组合(m的阶乘)某个数α
所有的排列组合(m+n)的阶乘 = 蓝色的球的排列组合(n的阶乘)乘以某个数β
剩下的(m+n)的阶乘/(m的阶乘*n的阶乘)就是去掉红球和蓝球排列的组合

C(m+n,m) = (m + n)! / (m+n-m)! * m!

Atcoder Context 125D.003.jpeg
3.接下来如何求得答案,这也是很令人头疼的问题
因为目标点坐标X,Y的数值最大可能达到10^6,相应的m,n的值也可能回答道10^5这么大,我们贸然的对如此大的数去做阶乘显然会overflow

那么我们怎么办呢,看看结果公式
(m + n)! / (m+n-m)! * m!
(m+n)的阶乘的时候我们可以每乘法一次,对10^9+7取余数,这样把数值控制在一个很小的范围以内

但是 /(m+n-m)! * m!这些怎么办

我们根据费马小定律有如下推断,如图
Atcoder Context 125D.004.jpeg

x^mod-2和1/x相互为对mod取余的逆元
求1/x对mod的余数就相当于求x^mod-2对mod的余数

结果可以看成((m + n)! (1/1 1/2 1/3 .... 1/(n)) (1/1 1/2 1/3 .... 1/(m)) } % mod
我们可以依次求的这些
1/1 % mod
1/2 % mod
1/3 % mod
1/x % mod
但是这样带来一个问题,mod=10^9+7非常大的一个数n^(mod-2)显然也不好计算

有另外一种算法
(1/x) % mod = - ((1/(mod % x) % mod) * (mod / x)
我们设定一个数组叫inverse

inverse[x] = (1/x) % mod
inverse[x] = (-1* inverse[mod % x] * (mod //2 i)) % mod
x=0的时候inverse[0] = 0
x=1的时候inverse[1] = 1
x=2的时候inverse[2] = (-1 * inverse[1] * (mod //2)) % mod
x=3的时候inverse[3] = (-1 * inverse[2] * (mod //3)) % mod
...

代码

X, Y = map(int,input().split())

def cmb(n, k, mod, fac, ifac):
    k = min(k, n-k)
    return fac[n] * ifac[k] * ifac[n-k] % mod


def make_tables(mod, n):
    fac = [1, 1] # 储存阶乘的数组
    ifac = [1, 1] # 储存逆元阶乘的数组
    inverse = [0, 1] # 储存逆元的数组

    for i in range(2, n+1):
        fac.append((fac[-1] * i) % mod)
        inverse.append((-inverse[mod % i] * (mod//i)) % mod)
        ifac.append((ifac[-1] * inverse[-1]) % mod)
    return fac, ifac


def comb(m,n):
    MOD = 10**9 + 7
    fac, ifac = make_tables(MOD, m)
    ans = cmb(m, n, MOD, fac, ifac)
    return ans

def calculate(x,y):
    m = 0
    n = 0

    if ((x + y) % 3) != 0:
        print(0)
        return

    result = []

    while (2*m <= x) and (m <= y):
        n = x - 2 * m
        if (2*m + n == x) and (2*n + m == y):
            result.append(comb(m+n,n))

        m = m + 1

    if len(result) == 0:
        print(0)
    else:
        print(min(result))

calculate(X,Y)

总结
这道题有3个难点

  1. 如何求出m,n的所有的组合
  2. 如何抽象出高中数学经典排列组合c(m+n,n)的问题
  3. 在mod数据量比较大的情况下,如何求得c(m+n,n) % mod

最后本篇文章所提交的代码里关于求排列组合的代码,是网上寻找的。有心的话,可以收藏,目前代码竞技的职业玩家在排列组合上应该也是使用的这些代码

※ 另外,我会在我的微信个人订阅号上推出一些文章,欢迎关注
二维码.jpg



伟大不DIAO
1 声望1 粉丝

Done is better than perfect