Algorithm 使用完全k翻转操作生成的长度为n的不同二进制序列数

Algorithm 使用完全k翻转操作生成的长度为n的不同二进制序列数,algorithm,Algorithm,考虑长度为N的二进制序列b。最初,所有位都设置为0。我们定义了一个具有两个参数的翻转操作,flip(L,R),这样: 索引在L和R之间的所有位都被“翻转”,这意味着值为1的位变为值为0的位,反之亦然。更确切地说,对于[L,R]:b[i]=范围内的所有i!b[i] 指定范围之外的位不会发生任何变化 要求您确定使用对任意给定数字进行模翻转操作(我们称之为MOD 更具体地说,每个测试在第一行包含一个数字T,即要给出的查询数。然后是T个查询,每个查询的形式为N,K,MOD,其含义来自上面 一,≤

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

  • 索引在L和R之间的所有位都被“翻转”,这意味着值为1的位变为值为0的位,反之亦然。更确切地说,对于[L,R]:b[i]=范围内的所有i!b[i]
  • 指定范围之外的位不会发生任何变化
要求您确定使用对任意给定数字进行模翻转操作(我们称之为MOD
更具体地说,每个测试在第一行包含一个数字T,即要给出的查询数。然后是T个查询,每个查询的形式为NKMOD,其含义来自上面

  • 一,≤ N、 K≤ 30万
  • T≤ 二百五十
  • 二,≤ 摩登派青年≤ 10000007
  • 测试中所有N-s的总和为≤ 60万
  • 时限:2秒
  • 内存限制:65536 KB

示例:
输入:
1
211000
输出:
3
说明:
只有一个查询。最初的顺序是00。我们可以执行以下操作:
翻转(1,1)⇒ 10
翻转(2,2)⇒ 01
翻转(1,2)⇒ 11
因此,有3种可能的序列可以使用1次翻转生成


我所做的一些快速观察,尽管我不确定它们是否完全正确:
如果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,但这并不是很大的区别。无论如何,很容易理解为什么会发生这种情况。我想这是值得注意的。

以下是一些想法:

  • 我们可以假设没有发生两次翻转操作(否则,我们可以假设它没有发生)。它确实会影响操作的数量,但我将在稍后讨论

  • 我们可以假设没有两段相交。事实上,如果
    L1
    ,我们可以只做
    (L1,L2-1)
    ,而
    (R1+1,R2)
    翻转。当一段位于另一段内部时,处理方式类似

  • 我们也可以假设没有两个部分相互接触。否则,我们可以将它们粘合在一起,减少操作数量

  • 这些观察结果给出了以下公式,即在不进行“冗余”翻转的情况下,通过准确翻转
    k
    段可以获得的不同序列数:C(n+1,2*k)(我们选择段的2*k端。它们总是不同的。左端是排他的)

  • 如果我们只执行了
    K
    flips,答案将是
    K=0…K的C(n+1,2*K)

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

  • 通过运行蛮力搜索(我知道这不是一个真正的证据,但结合上面提到的观察结果,看起来是正确的),如果n或k等于1,那么这个和减去1,反之则正好是和

  • 也就是说,结果是
    C(n+1,0)+C(n+1,2)+…+C(n+1,2*K)-d
    ,其中,如果
    n=1或K=1,则
    d=1
    ,否则
    0

    下面是我用来查找运行蛮力搜索的模式并验证公式是否适用于小
    n
    k
    的代码:

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

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

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

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

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


  • 我们来确定一个素数p。让我们假设
    p^a | MOD
    (也就是说,我们需要得到结果模
    p^a
    )。我们可以预先计算阶乘的所有
    p
    -自由部分,以及将所有
    0的阶乘除以的
    p
    的最大幂,你已经在一条具有二项式系数的好路径上了。有几个因素需要考虑:

    把你的数字想象成一个l的二进制字符串
    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
    
    [0, 1, 0, 0, 1]   number
    [a, b, c, d, e]   number of flips.
    
    [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]
    
    v = (k % 2 == n % 2) ? n : n - 1
    
    v = k
    
    noOfAvailableFlips:
        if k < n:
            return k
        else:
            return (k % 2 == n % 2) ? n : n - 1
    
    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
    
    # 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