【问题标题】:Count number of subsets having cumulative XOR less than k计算累积异或小于 k 的子集数
【发布时间】:2019-05-19 01:33:04
【问题描述】:

给定一组大小为 N 的数字 S。计算 S 的所有子集,其中子集元素的累积异或小于 K。

我可以想到蛮力方法来生成 S 的所有子集并计算具有小于 k 的累积 XOR 元素的子集。我正在寻找优化的解决方案而不生成 S 的所有子集,我可以找到所有这样的子集

Example: 
S = {1,2}
K = 4
U = {{},{1},{2},{1,2}}
Answer is 4 As 
cumulative XOR values are 0 for {}, 1 for {1}, 2 for {2}, 3 for {1,2}.

【问题讨论】:

  • 棘手。我唯一能想到的就是记下 K 的最高设置位,比如位 T。因此 K T,都太大了。
  • 有些数字(“原始”或“子集 XOR”)只有位 B

标签: algorithm set dynamic-programming counting


【解决方案1】:

根据问题的限制,有几种可行的方法来解决它:

  • 正如您所指出的,如果 N 足够小,尝试 O(2^N) 中所有可能的子集将产生预期的结果。
  • 如果 S 中的值受到某个足够小的值的限制,您可以使用士官长的帖子中概述的动态编程解决方案。
  • 如果 N 都很高,并且 S 中的值很大(十亿及以上),则可以应用更复杂但多项式时间的方法。大纲如下:

让我们看一下 S 中数字的二进制表示,例如对于数字 17、5、20、14,它将是:

10001 17
00101  5
10100 20
01110 14

对于 K 也是如此,例如让我们取 K=11: 01011 11

如果我们想计算有多少子集 XOR 到恰好 K,我们可以将问题表示为模 2 的线性方程组,其中变量与 S 中的数字一样多,并且许多方程式,因为我们的数字中有有意义的位。更具体地说,让第 i 个方程表示约束“S 的子集中的第 i 位数字的 XOR 应该等于 K 中的第 i 位”。 (请注意,XOR 操作相当于求和模 2)。例如,对于最小(最右边)位,我们有以下内容:x1 * 1 + x2 * 1 + x3 * 0 + x4 * 0 = 1 (mod 2),其中 x_j 为 0 或 1,具体取决于我们是否在子集中包含第 j 个数字。

请注意,这个方程组可能有 0、1 或多个解。在有很多解的情况下,每个自变量可以取0或1,因此解的数量是2^(自变量)。

我们可以使用高斯消元法检测自变量的数量和线性方程组的可解性,该消元法在 O(n^3) 中运行,用于大小为 n 的方阵 - 在您的情况下,矩阵不是方阵,所以我们可以使用较大的(|S|, log(max(S)) 来估计复杂度。

太好了,现在我们可以遍历从 0 到 K-1 的所有 K',分别解决问题,并对结果求和。然而,这并不比动态编程解决方案好,而且在运行时只是伪多项式。让我们进行另一个可以产生多项式解的观察:我们只对O(logK) 不同的方程组感兴趣,以计算有多少子集异或到小于K

让我们将K 中的最高非零位位置表示为 B。如果 B 之上的所有位和位 B 在我们取的子集的 XOR 中都等于 0,那么显然它会更少比K。因此,我们的第一个方程组可以只写上述位,而忽略 B 以下的所有内容。

现在让我们看看如果我们允许第 B 位等于 1 会发生什么。如果在编号 K 中有一个或多个零位跟随第 B 位,它们都必须是 0也产生异或。如果在我们的 XOR 中第一个后续非零位 B2 设置为 0,那么它将小于 K。我们可以通过说“B 以上的所有位都是 0,位 B 是1,B和B2之间的所有位都是0,位B2是0”并计算它的解数。

如果我们继续这样直到K中的最小二进制位置,我们最多需要建立logK方程组,并得到我们想要的结果。

这种方法的复杂性类似于O(logK * max(n, logK)^3),尽管取决于实现,高斯消元法对非方阵的工作速度更快。

【讨论】:

    【解决方案2】:

    问题与count of subsets having sum equal to k 非常相似。 我们可以以类似的方式继续,并将总和等于 0 到 k 的子集的计数相加。

    下面是我的 python 实现。

    它使用动态编程将一些中间结果存储在 DP 表的每个单元格中。单元格 dp[i][j] 包含等于 j 的子集计数,可以使用排序数组中的第一个 ith 数字形成。

    时间复杂度O(n * maxXor),其中maxXormaximum value which can be achieved by xoring any of the numbers in the array。最大 maxXor 将等于大于 maxValue present in arrayK 的 2 的最小幂

    from math import floor, log
    
    
    arr = [1, 2]
    K = 4
    
    
    def getCoundDp(arr, k):
        arr.sort()
        maxVal = arr[-1]
        maxXor = 2**(floor(log(max(maxVal, k), 2)) + 1)
        dp = [[0 for i in range(maxXor)] for a in arr]
        dp[0][0] = 1
        # in the 1st row, mark the arr[0] to have count 1
        dp[0][arr[0]] = 1
        for row in range(1, len(arr)):
            for col in range(maxXor):
                dp[row][col] += dp[row-1][col]
                neededXor = col ^ arr[row]
                dp[row][col] += dp[row-1][neededXor]
        return sum(dp[-1][:k])
    
    
    print(getCoundDp(arr, K))
    

    您对生成和检查所有子集的建议会很慢O(2^n)。但至少对于验证更快的实现来说仍然是有价值的。下面是使用itertools.combination 的python 蛮力方法示例,您可以阅读更多关于它的信息here

    from itertools import combinations
    
    
    def getXor(arr):
        xor = 0
        for i in arr:
            xor ^= i
        return xor
    
    
    def getCountBruteForce(arr, k):
        arr.sort()
        countLessThanK = 0
        for r in range(0, len(arr)+1):
            for comb in combinations(arr, r):
                xor = getXor(comb)
                if xor < k:
                    countLessThanK += 1
        return(countLessThanK)
    

    【讨论】:

    • 我认为您的代码不适用于最小输入数字 X 大于或等于 K 的情况。仅使用第一个数字获得 X 的方法数等于 1,但您赢了'不反映在 dp[0][X] 元素中。
    • @eldar。你能提供一个示例输入吗?我很乐意纠正这个。我没有对每一种输入进行测试,所以它可能有一些错误。 :)
    • 例如,如果 S = {6, 4, 4, 4} 并且 K=4,当子集的实际数量更高时(任意两个 4,{6}+任何 4, {6, 4, 4, 4})。
    • (本质上我是说dp[0][arr[0]] = 1赋值应该被执行,不管arr[0]是否小于K。)
    • @eldar,你是正确的。我检查了我可以确认它是错误的。我已经更新了代码 sn-p。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-18
    • 1970-01-01
    • 2019-06-06
    • 1970-01-01
    • 2012-02-11
    • 2022-01-17
    相关资源
    最近更新 更多