确定(x^2 + x + 1)^n中x^m项的系数是偶数还是奇数

9
给定整数 nm,确定在 (x^2+x+1)^nx^m 项的系数是偶数还是奇数?

例如,如果 n=3,m=4,则 (x^2+x+1)^3 = x^6 + 3x^5 + [[6x^4]] + 7x^3 + 6x^2 + 3x + 1,因此 x^4 项的系数为 6(=偶数)。

nm 可以达到 10^12,我希望在几秒钟内计算出结果,因此无法使用线性时间计算。

你有什么有效的算法吗?

x^m系数”是什么意思? - trincot
@trincot 添加了示例。 - square1001
2
@VincentvanderWeele 真的吗?并不是所有的问题都是数学问题。这是一个编程问题,因为我想要更快地计算它,而不是在一对值中花费10或20分钟。这是一个算法问题。 - square1001
如果问题是这样问的,你可以非常确定你不必计算实际系数。我可能会先将所有内容转换为二进制,并查找哪些项可以直接消除。 - biziclop
4个回答

6

首先需要注意,如果一个人只关心x^m系数的奇偶性,那么可以将多项式的系数视为有限域F2的元素。

注意:(1+x+x^2)^2 = (1+x^2+x^4) mod 2,因为交叉项都是偶数。实际上,如果n是2的幂,则(1+x+x^2)^n = (1 + x^n + x^{2n}) mod 2

对于一般的n,将它表示成2的幂次之和(即二进制形式)。

n = (2^a1 + 2^a2 + 2^a3 + ... + 2^ak).

然后将与每个2的幂对应的幂次乘在一起:

(1+x+x^2)^n = (1+x^(2^a1)+x^(2^(a1+1))) * ((1+x^(2^a2)+x^(2^(a2+1))) * ...

现在,这个产品中的每个术语只有3个因素,如果n被限制在10 ^ 12以下,最多可能有35或36个术语。因此,将它们相乘很容易。

这是一些执行此操作的Python代码:

# poly_times computes the product of polynomials
# p and q over the field F2. They are each
# represented by a set of non-zero coefficients.
# For example set([0, 2, 5]) corresponds to x^0 + x^2 + x^5.
def poly_times(p, q):
    result = set()
    for i in p:
        for j in q:
            if i+j in result:
                result.remove(i+j)
            else:
                result.add(i+j)
    return result

# Return the coefficient of x^m in (1+x+x^2)^n.
def coeff(n, m):
    prod = set([0])
    i = 0
    while n:
        if n % 2:
            prod = poly_times(prod, [0, 2**i, 2**(i+1)])
        i += 1
        n >>= 1
    return 1 if m in prod else 0

for m in xrange(10):
    print m, coeff(3, m)

print coeff(1947248745, 1947248745034)    

优化

当n的位数很大时,随着n接近10^12,这种方法会变得太慢。

但是,可以通过将多项式幂分成两部分来大大加速计算速度,在最后一步中找到m的系数,并不是通过进行完整的多项式乘法,而是通过计算每个部分中对总和为m的系数对的数量。下面是经过优化的coeff

# poly_times computes the product of polynomials
# p and q over the field F2. They are each
# represented by a set of non-zero coefficients.
# For example set([0, 2, 5]) corresponds to x^0 + x^2 + x^5.
# Coefficients larger than m are discarded.
def poly_times(p, q, m):
    result = set()
    for i in p:
        for j in q:
            if i + j > m:
                continue
            if i+j in result:
                result.remove(i+j)
            else:
                result.add(i+j)
    return result

# Return the coefficient of x^m in (1+x+x^2)^n.
def coeff(n, m):
    if m > 2*n: return 0
    prod = [set([0]), set([0])]
    i = 0
    while n:
        if n % 2:
            prod[i//20] = poly_times(prod[i//20], [0, 2**i, 2**(i+1)], m)
        i += 1
        n >>= 1
    s = 0
    for x in prod[0]:
        s += m-x in prod[1]
    return s % 2

for m in xrange(10):
    print m, coeff(3, m)

print coeff(0xffffffffff, 0xffffffffff)

请注意,此程序可以在几秒钟内计算出coeff(0xffffffffff, 0xffffffffff),而0xffffffffff大于10**12。

黑客案例:n=2^35-1,m=2^35-1(我认为奇数系数的数量会很多)。 - square1001
在最坏情况下,它肯定是线性的,但由于如果n有很多位被设置,则有很多公共项,因此计算起来相当困难。但我认为问题是关于找到一个在几秒钟内运行的解决方案,而不是关于计算复杂度的?我从问题中猜测这是一种在线评测类型的问题。如果速度不够快,那么即使超出了我在问题中提出的几个建议,也有很多优化这个解决方案的空间。 - Paul Hankin
我认为你说得对,当n有许多位被设置时,它会变得太慢。 - Paul Hankin
我有一个优化方案,可以使它运行得非常快。 - Paul Hankin
好的,现在当n=2^40-1,m=2^40-1时需要4秒钟。 - Paul Hankin

4

是的,输入位数的时间复杂度是线性的。

所涉及的系数是三项式系数 T(n, m)。对于二项式系数,我们可以使用卢卡斯定理;让我们计算p = 2的三项式类比。

mod 2条件下,按照Nathan Fine的证明方法进行操作。

(1 + x + x^2)^{2^i} = 1 + x^{2^i} + x^{2^{2 i}}

(1 + x + x^2)^n
    = prod_i ((1 + x + x^2)^{2^i n_i})
        where n = sum_i n_i 2^i and n_i in {0, 1} for all i
        (i.e., n_i is the binary representation of n
    = prod_i (1 + x^{2^i n_i} + x^{2^i 2 n_i})
    = prod_i sum_{m_i = 0}^{2 n_i} x^{2^i}
    = sum_{(m_i)} prod_i x^{2^i m_i}
        taken over sequences (m_i) where 0 ≤ m_i ≤ 2 n_i.

在二项式情况下,接下来的步骤是观察到对于 x^m 的系数,至多只有一种选择 (m_i),其 x^{2^i m_i} 因子具有正确的乘积,即 m 的二进制表示。
在三项式情况下,我们必须考虑二进制伪表示 (m_i)m,其中伪比特可以是零、一或两个。当且仅当对于所有 i,使得 n_i = 0,我们有 m_i = 0 时,才会对总和做出贡献。
我们可以编写一个自动机,逐位扫描 nm。状态 a 是初始和接受状态。
a (0:0:nm') -> a nm'    [emit 0]
a (1:0:nm') -> a nm'    [emit 0]
            -> b nm'    [emit 2]
a (1:1:nm') -> a nm'    [emit 1]

b (0:1:nm') -> a nm'    [emit 0]
b (1:0:nm') -> b nm'    [emit 1]
b (1:1:nm') -> a nm'    [emit 0]
            -> b nm'    [emit 2]

我们可以使用动态规划来计算路径数量。代码形式如下:
def trinomial_mod_two(n, m):
    a, b = 1, 0
    while m:
        n1, n = n & 1, n >> 1
        m1, m = m & 1, m >> 1
        if n1:
            if m1:
                a, b = a ^ b, b
            else:
                a, b = a, a ^ b
        elif m1:
            a, b = b, 0
        else:
            a, b = a, 0
    return a

这是一个有趣的无分支版本:

def trinomial_mod_two_branchless(n, m):
    a, b = 1, 0
    while m:
        n1, n = n & 1, n >> 1
        m1, m = m & 1, m >> 1
        a, b = ((n1 | ~m1) & a) ^ (m1 & b), ((n1 & ~m1) & a) ^ (n1 & b)
    return a

1
非常聪明,我希望我能想到这种方法。不过,我认为有一种更清晰的方法来解释它。关于计算从n的某些位(或将这些位向左移动一次)相加的组合构造m的方法。 - Paul Hankin
哇!非常棒的解决方案! - square1001

3
系数取决于从 x² + x + 1 中选择 n 项使所选项的幂之和为 m 的方式数量。这些方式可以分组,每组具有相同数量的已选 x² 项和 x 项(1 的选择次数由此得出)。
设 a 为 x² 项的数量,b 为 x 项的数量,c 为特定组中 1 的数量,则有以下等式成立:
2a + b = m        a + b + c = n
显然,一般情况下会有几个不同值的 a、b、c 组。一旦确定了 a,b 和 c 的值也就随之确定。因此,只需迭代可能的 a 值即可获得所有组。
如果您要编写一个暴力算法来获取系数本身,它在Python中的代码如下:
def combi(n, k): # number of ways to take k elements from n elements
    import math
    f = math.factorial
    return f(n) // f(k) // f(n-k)    

def get_coeff(n, m):
    if m > n * 2 or n < 0 or m < 0: # basic argument check
        return None
    if m > n: # mirrored situation is the same
        m = 2*n - m            
    coeff = 0
    for a in range(0, m//2+1):
        b = m - 2*a
        coeff += combi(n, a) * combi(n-a, b)
    return coeff

函数combi(n, k)将返回从n个元素中取k个元素的方式数量,即二项式系数
两次调用combi的乘积回答了以下问题:
我可以以多少种方式取a项和bx项?请注意,一旦选择了其他2个选项,常数项可以被取为1。
现在,由于我们只需要知道最终系数是奇数还是偶数,因此我们也只需要知道二项式系数是奇数还是偶数。 如math.stackexchange.com所解释的那样,这可以通过简单的位操作确定。
def combi_parity(n, k):
    return (k & (n-k)) == 0

def get_coeff_parity(n, m):
    if m > n * 2 or n < 0 or m < 0: # basic argument check
        return None
    if m > n:
        m = 2*n - m # mirrored situation is the same
    coeff_odd = 0
    for a in range(0, m//2+1):
        b = m - 2*a
        # A product is odd only when both factors are odd (so we perform an "and")
        # A sum changes parity whenever the added term is odd (so we perform a "xor")
        coeff_odd ^= combi_parity(n, a) and combi_parity(n-a, b) 
    return coeff_odd

repl.it上运行它。


点赞。我也尝试过这种方法,但对于大的m,n无法快速处理。我甚至进行了一些优化,使得“a”仅在满足“combi_parity(n,a)==1”的数字范围内变化。这是我的C代码,仍在我的笔记本电脑上运行:https://gist.github.com/paulhankin/5f5aa05231ce8659b6b040d849fd800e。 - Paul Hankin
你对c(n,k)的公式推导与我使用的n&k==k等价,但更好。我将在我的程序中借鉴它! - Paul Hankin
你的测试用例中没有使用大数:2^40-1 应该是 2**40-1。抱歉,我混淆了 Python 和数学符号。2^40-1 实际上是 37 ;( - Paul Hankin
啊哈,谢谢!我已经更新了参考链接中的示例调用。 - trincot
我的嵌套循环实际上是一种(尝试的)优化,它使a(在我的代码中称为k)仅在满足 combi_parity(n, a) 为True的数字上进行步进。 - Paul Hankin
好的,我错过了。 - trincot

2
好的,我想到了一个解决方案。以下是步骤:
  1. 将方程写成 n 次,例如 (a.x^2 + b.x^1 + c).(a.x^2 + b.x^1 + c)...n。这里的 a、b 和 c 是常数。
  2. 现在,我们需要从每个式子中挑选一项,使得所有这些项的乘积等于 x^m。
  3. 我们可以说,我们需要找到方程 t1.2 + t2 = m 的解,其中 t1 表示 x^2 的出现次数,t2表示x的出现次数。这是因为此时 t1t2 会将该项变为形如 k.x^m(k 为常数)。这相当于找到该方程的整数丢番图解,即找到所有满足 {t1, t2} 的解。
  4. 现在,我们需要进行一些排列来找到系数。假设你已经得到了前面一步的解之一,比如 {1,2},那么对于这个二元组,系数将为 (1^1.nC1).(2^2.(n-1)C2),这将成为系数的一个组成部分。如果你将所有与丢番图解对应的系数项相加,就能得到系数。

以上算法的实现需要一些时间,所以我只发布了步骤。

注意:我进行了一些搜索,并发现有各种各样的丢番图解算法。以下是一个相关的帖子:如何在编程中解决线性丢番图方程?

编辑:由于要求提供一个示例,

  1. 假设我们有方程式 (x^2 + x^1 + x^1)^3,我们要找到 x^3 的系数。因此,我们有 m = 3
  2. 将方程式分开写是为了更好地看到步骤。因此,它是:

    (x^2 + x^1 + x^1).(x^2 + x^1 + x^1).(x^2 + x^1 + x^1)

  3. 现在,我们想从每个方程式中选取 {x^2,x^1,1} 中的任意一个。有多种选择方法,使得它们相乘的形式为 x^3

  4. 为了解决这个问题,我们可以写出方程式 2.a + b = 3,其中a表示选取x^2的次数,b表示选取x的次数。这个方程式的解是,{0, 3}{1, 1}。由于我们还要考虑选取它们的顺序,所以下一步我们将应用组合数学。

  5. 系数将是 2^0.3C0.3^3.3C3 + 2^1.3C1.3^1.2C1。现在,在第一项中,2^0 是方程式中 x^2 的系数,3C0 意味着选取 0 个 x^23C3 意味着选取 3 个 x,这将给出 x^3,但我们还要乘以 2^0,因为 2 是方程式中 x^2 的系数,类似地,乘以 3^3,因为 3 是 x 的系数。类似地,你可以按照与 {1, 1} 相应的第二项处理。

  6. 这加起来是 63,你可以手动计算并验证您会得到 63。

希望我表述清楚。


我不明白。你能给我一个例子吗? - square1001
@square1001:我正在准备示例。请给我几分钟时间。 - Manish Kumar Sharma
@square1001:请看示例。 - Manish Kumar Sharma
但是如果n和m很大,你就不能暴力枚举{a, b}。 - square1001
总结一下这个方法:计算sum(c(n, k)c(n-k, m-2k) for k=0..m/2)。通过对2取模来计算,注意到只有当a&b==b时c(a, b)=1 mod 2(因此您只需要迭代具有子集位的k,这些位是n的位)。但是当n和m很大时,我无法使C版本运行得足够快。如果您想尝试我的代码:https://gist.github.com/paulhankin/5f5aa05231ce8659b6b040d849fd800e - Paul Hankin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接