【问题标题】:Number of different binary sequences of length n generated using exactly k flip operations使用恰好 k 个翻转操作生成的长度为 n 的不同二进制序列的数量
【发布时间】:2016-11-06 23:27:22
【问题描述】:

考虑一个长度为 N 的二进制序列 b。最初,所有位都设置为 0。我们定义了一个带有 2 个参数的翻转操作,flip(L,R),这样:

  • 索引在 L 和 R 之间的所有位都被“翻转”,这意味着值为 1 的位变为值为 0 的位,反之亦然。更准确地说,对于 [L,R] 范围内的所有 i:b[i] = !b[i]。
  • 超出指定范围的位不会发生任何事情。

您被要求确定可以使用 exactly K 翻转操作对任意给定数字取模获得的可能不同序列的数量,我们称之为 MOD
更具体地说,每个测试在第一行包含一个数字 T,即要给出的查询数。然后是T个查询,每个查询的形式为NKMOD,含义同上。

  • 1≤N,K≤300 000
  • T≤250
  • 2 ≤ MOD ≤ 1 000 000 007
  • 测试中所有 N-s 的总和≤600 000
  • 时间限制:2秒
  • 内存限制:65536 KB

示例:
输入:
1
2 1 1000
输出:
3
说明:
有一个查询。初始序列为00。我们可以进行以下操作:
翻转(1,1) ⇒ 10
翻转(2,2) ⇒ 01
翻转(1,2) ⇒ 11
因此,只需 1 次翻转就可以生成 3 种可能的序列。


我所做的一些快速观察,虽然我不确定它们是否完全正确:
如果 K 足够大,也就是说,如果我们有足够多的翻转次数可供使用,我们应该能够获得 2n 个序列。
如果 K=1,那么我们要寻找的结果是 N(N+1)/2。它也是 C(n,1)+C(n,2),其中 C 是二项式系数。
目前正在尝试一种蛮力方法,看看我是否能发现某种规则。我认为这是一些二项式系数的总和,但我不确定。
我还遇到过这个问题的一个更简单的变体,其中翻转操作只翻转一个指定的位。在这种情况下,结果是 C(n,k)+C(n,k-2)+C(n,k-4)+...+C(n,(1 或 0))。当然,也有 k > n 的特殊情况,但差别不大。无论如何,很容易理解为什么会发生这种情况。我想这是值得注意的。

【问题讨论】:

  • 所以所有翻转都是非空范围?
  • @JaredGoguen 我想是的,是的。问题实际上并没有说明什么,但是如果您无法使用单次翻转(必须翻转一个空范围)获得序列“00”,那么我很确定您只能进行翻转非空范围。
  • 我“几乎”有了解决问题的办法。我不怎么做的唯一部分是有效地计算以非素数为模的二项式系数。你对它感兴趣吗?
  • @kraskevich 当然。我认为,模乘逆可以解决这个问题。
  • 看到这个类似的问题:stackoverflow.com/questions/37680863/…

标签: algorithm


【解决方案1】:

这里有一些想法:

  1. 我们可以假设没有发生两次翻转操作(否则,我们可以假设它没有发生)。确实会影响操作的次数,不过后面会讲到。

  2. 我们可以假设没有两个线段相交。事实上,如果L1 < L2 < R1 < R2,我们可以只做(L1, L2 - 1)(R1 + 1, R2) 翻转来代替。一个段在另一个段内的情况类似处理。

  3. 我们也可以假设没有两个段相互接触。否则,我们可以将它们粘合在一起并减少操作次数。

  4. 这些观察给出了以下公式:通过精确翻转 k 片段而无需“冗余”翻转可以获得不同序列的数量:C(n + 1, 2 * k)(我们选择 2 * k段的末端。它们总是不同的。左端是排他的)。

  5. 如果我们执行的翻转次数不超过K,答案将是
    sum for k = 0...K of C(n + 1, 2 * k)

  6. 直观地说,似乎可以将不超过K翻转的序列转换为恰好K翻转的序列(例如,我们可以将同一段再翻转两次并添加2操作。我们也可以将一个包含两个以上元素的段拆分为两个段并添加一个操作)。

  7. 通过运行蛮力搜索(我知道这不是真正的证明,但结合上面提到的观察结果看起来是正确的),如果 n 或 k 等于 1,则这个和减去 1 的答案正好是否则求和。

也就是说,结果是C(n + 1, 0) + C(n + 1, 2) + ... + C(n + 1, 2 * K) - d,如果n = 1 or k = 1,则为d = 1,否则为0

这是我用来查找运行蛮力搜索的模式并验证该公式对于小型 nk 是否正确的代码:

reachable = set()
was = set()


def other(c):
    """
    returns '1' if c == '0' and '0' otherwise
    """
    return '0' if c == '1' else '1'


def flipped(s, l, r):
    """
    Flips the [l, r] segment of the string s and returns the result
    """
    res = s[:l]
    for i in range(l, r + 1):
        res += other(s[i]) 
    res += s[r + 1:]
    return res


def go(xs, k):
    """
    Exhaustive search. was is used to speed up the search to avoid checking the
    same string with the same number of remaining operations twice.
    """ 
    p = (xs, k)
    if p in was:
        return
    was.add(p)
    if k == 0:
        reachable.add(xs)
        return
    for l in range(len(xs)):
        for r in range(l, len(xs)):
            go(flipped(xs, l, r), k - 1)


def calc_naive(n, k):
    """
    Counts the number of reachable sequences by running an exhaustive search
    """
    xs = '0' * n
    global reachable
    global was
    was = set()
    reachable = set()
    go(xs, k)
    return len(reachable)


def fact(n):
    return 1 if n == 0 else n * fact(n - 1)


def cnk(n, k):
    if k > n:
        return 0
    return fact(n) // fact(k) // fact(n - k)


def solve(n, k):
    """
    Uses the formula shown above to compute the answer
    """
    res = 0        
    for i in range(k + 1):
        res += cnk(n + 1, 2 * i)
    if k == 1 or n == 1:
        res -= 1
    return res


if __name__ == '__main__':
    # Checks that the formula gives the right answer for small values of n and k
    for n in range(1, 11):
        for k in range(1, 11):   
            assert calc_naive(n, k) == solve(n, k)

此解决方案比穷举搜索要好得多。例如,如果我们使用帕斯卡三角形计算系数,它可以在每个测试用例中运行O(N * K) 时间。不幸的是,它不够快。我知道如何更有效地解决素数MOD(使用卢卡斯定理),但 O 在一般情况下没有解决方案。

乘法模逆不能立即解决这个问题,因为k!(n - k)! 可能没有逆模MOD

注意:我假设C(n, m) 是为所有非负的nm 定义的,如果n < m 等于0

我想我现在知道如何解决任意MOD 的问题了。

  1. 让我们将MOD 分解为素因数p1^a1 * p2^a2 * ... * pn^an。现在可以对每个素因数独立解决这个问题,并使用中国剩余定理组合结果。

  2. 让我们修正一个素数 p。让我们假设p^a|MOD(也就是说,我们需要得到模p^a的结果)。我们可以使用以下方法预先计算所有p-free 阶乘部分和p 的最大功率,它在线性时间内将所有0 <= n <= N 的阶乘除以:

    powers = [0] * (N + 1)
    p_free = [i for i in range(N + 1)]
    p_free[0] = 1
    for cur_p in powers of p <= N:
        i = cur_p
        while i < N:
            powers[i] += 1
            p_free[i] /= p
            i += cur_p
    

    现在,阶乘的p-free 部分是所有i &lt;= np_free[i] 的乘积,而p 除以n! 的幂是powers 的前缀和。

  3. 现在我们可以划分两个阶乘:p-free 部分与p^a 互质,所以它总是有逆。 p 的幂只是减去了。

  4. 我们快到了。另一项观察:我们可以在线性时间内预先计算p-free 部分的逆。让我们使用欧几里得算法计算N! 的无 p 部分的逆。现在我们可以将所有iN迭代到0。i!p-free部分的倒数是i + 1乘以p_free[i]的倒数(如果我们重写,很容易证明这一点使用元素与p^a 互质这一事实,将无 p 部分的倒数作为乘积在乘法下形成一个阿贝尔群。

  5. 此算法在每个测试用例的 O(N * number_of_prime_factors + the time to solve the system using the Chinese remainder theorem + sqrt(MOD)) 时间内运行。现在看起来已经足够好了。

【讨论】:

  • 是的,我最终非常接近我的蛮力方法;我最终得到了从 0 到 2k 的 i 的 C(n,i) 总和,但那是因为我犯了一个小错误。无论如何,公式可能是正确的。
  • 然后是实际计算二项式系数的问题。啊,真高兴。该问题最初要求您回答模 1e9 + 7,这显然会更容易一些。然而,作者在讨论后决定将其更改为任意模数,并意识到新条件会使问题变得更难 D:。
  • @k4andrei 我找到了一种解决任何模数的方法。这很艰难。
  • 应该很难。这是为我国的 JBOI 2016 参与者“准备”的问题。
  • 啊,是的,中国剩余定理。哦,好吧,我想应该这样做。但是有一个问题,阶乘的无 p 部分是什么意思?
【解决方案2】:

您已经在使用二项式系数的好道路上。有几个因素需要考虑:

将您的号码视为长度为n 的二进制字符串。现在我们可以创建另一个数组来计算一个位将被翻转的次数:

[0, 1, 0, 0, 1]   number
[a, b, c, d, e]   number of flips.

但偶数次翻转都会导致相同的结果,所有奇数次翻转也是如此。所以基本上分布的相关部分可以表示为%2

下一个逻辑问题:有多少种不同的偶数和奇数组合可用。稍后我们将处理排序问题,为了简单起见,现在假设翻转数组是按降序排列的。我们从k 作为数组中唯一的翻转数开始。现在我们要添加一个翻转。由于使用了整个翻转数组 %2,我们需要从 k 的值中删除两个来实现这一点,并将它们分别插入到数组中。例如:

[5, 0, 0, 0]    mod 2 [1, 0, 0, 0]
[3, 1, 1, 0]          [1, 1, 1, 0]
[4, 1, 0, 0]          [0, 1, 0, 0]

如上一个示例所示(请记住,我们在最终结果中使用模 2),移动单个 1 不会改变最终结果中的翻转次数。因此,我们总是必须翻转翻转数组中的偶数位。如果k 是偶数,那么翻转的位数也一样,反之亦然,无论n 的值是多少。

所以现在的问题当然是有多少种不同的填充数组的方法可用?为简单起见,我们将立即从 mod 2 开始。
显然,如果k 是奇数,我们从 1 个翻转位开始,否则从 1 开始。我们总是添加 2 个翻转位。我们可以继续这样做,直到我们翻转所有 n 位(或至少我们可以翻转的所有位)

v = (k % 2 == n % 2) ? n : n - 1

否则我们无法将k 进一步传播到数组中。

v = k

把这些放在一起:

noOfAvailableFlips:
    if k < n:
        return k
    else:
        return (k % 2 == n % 2) ? n : n - 1

到目前为止,总有 v / 2 翻转数组(mod 2)在翻转位数上有所不同。现在我们进入下一部分排列这些数组。这只是一个简单的排列函数(准确地说是重复排列):

flipArrayNo(flippedbits):
    return factorial(n) / (factorial(flippedbits) * factorial(n - flippedbits)

把它们放在一起:

solutionsByFlipping(n, k):
    res = 0
    for i in [k % 2, noOfAvailableFlips(), step=2]:
        res += flipArrayNo(i)

    return res

这也表明,对于足够大的数字,我们无法获得 2^n 个序列,原因很简单,我们不能随意安排操作。实际影响结果的翻转次数将始终是偶数或奇数,具体取决于k。没有办法解决这个问题。可以得到的最好结果是2^(n-1) 序列。

【讨论】:

    【解决方案3】:

    为了完整起见,这里是一个动态程序。它可以轻松处理任意模数,因为它基于总和,但不幸的是,我还没有找到一种方法来加速它超过O(n * k)

    a[n][k] 为长度为n 的二进制字符串的数量,其中k 不相邻的连续1s 块以1 结尾。令b[n][k] 为长度为n 的二进制字符串的数量,其中k0 结尾的不相邻的1s 块。

    然后:

    # we can append 1 to any arrangement of k non-adjacent blocks of contiguous 1's 
    # that ends in 1, or to any arrangement of (k-1) non-adjacent blocks of contiguous
    # 1's that ends in 0:
    
    a[n][k] = a[n - 1][k] + b[n - 1][k - 1]
    
    # we can append 0 to any arrangement of k non-adjacent blocks of contiguous 1's 
    # that ends in either 0 or 1:
    
    b[n][k] = b[n - 1][k] + a[n - 1][k]
    
    # complete answer would be sum (a[n][i] + b[n][i]) for i = 0 to k
    

    我想知道以下观察是否有用:(1) a[n][k]b[n][k]n &lt; 2*k - 1 时为零,以及 (2) 另一方面,k 的值大于 ⌊@987654337 @⌋ 总体答案似乎是相同的。

    Python 代码(为简单起见定义了完整的矩阵,但我认为对于自下而上的方法,实际上每个矩阵只需要一行):

    a = [[0] * 11 for i in range(0,11)]
    b = [([1] + [0] * 10) for i in range(0,11)]
    
    def f(n,k):
      return fa(n,k) + fb(n,k)
    
    def fa(n,k):
      global a
    
      if a[n][k] or n == 0 or k == 0:
        return a[n][k]
    
      elif n == 2*k - 1:
        a[n][k] = 1
        return 1
    
      else:
        a[n][k] = fb(n-1,k-1) + fa(n-1,k)
        return a[n][k]
    
    def fb(n,k):
      global b
    
      if b[n][k] or n == 0 or n == 2*k - 1:
        return b[n][k]
    
      else:
        b[n][k] = fb(n-1,k) + fa(n-1,k)
        return b[n][k]
    
    def g(n,k):
      return sum([f(n,i) for i in range(0,k+1)])
    
    # example
    print(g(10,10))
    
    for i in range(0,11):
      print(a[i])
    
    print()
    
    for i in range(0,11):
      print(b[i])
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-22
      • 2014-05-30
      • 2018-04-17
      • 1970-01-01
      • 2016-10-07
      • 2010-12-23
      • 2011-12-04
      相关资源
      最近更新 更多