给定一个长度为 n 的数组,找到子集的 XOR 等于给定数字的子集数

Posted

技术标签:

【中文标题】给定一个长度为 n 的数组,找到子集的 XOR 等于给定数字的子集数【英文标题】:Given an array of length n, find number of subsets where XOR of a subset is equal to a given number [closed] 【发布时间】:2015-12-08 05:34:39 【问题描述】:

给定一个长度为n 的数组arr,找出有多少arr 的子集使得这些子集中的XOR(^) 等于给定的数字ans

我有这种dp 方法,但是有没有办法提高它的时间复杂度。 ans 总是小于 1024。

这里ans 是第。这样子集的XOR(^) 等于它。 arr[n] 包含所有数字

memset(dp, 0, sizeof(dp));
dp[0][0] = 1;

for(i = 1; i <= n; i++)
    for(j = 0; j < 1024; j++) 
        dp[i][j] = (dp[i-1][j] + dp[i-1][j^arr[i]]);
    


cout << (dp[n][ans]);

【问题讨论】:

你的问题不够清楚。 dp 是正确的方法。您面临的问题是什么? @MohitJain 如何提高它的时间复杂度 我得到 TLE。 N 的数量级为 10^5。 那么您似乎应该能够利用输入数组中的重复。假设输入有 10^5 个数字,但只有 10^3 个唯一值是可能的,那么平均每个值在输入中出现 100 次。 XOR 的特性之一是,将一个数与自身异或偶数次产生 0,奇数次产生该数。结合这两个观察结果,您应该能够大大加快速度。 【参考方案1】:

来自user3386109 的评论,建立在您的代码之上:

/* Warning: Untested */
int counts[1024] = 0, ways[1024];
for(int i = 1; i <= n; ++i) counts[ arr[i] ] += 1;
for(int i = 0; i <= 1024; ++i) 
  const int z = counts[i];
  // Look for overflow here
  ways[i] = z == 0 ?
              0 :
              (int)(1U << (z-1));


memset(dp, 0, sizeof(dp));
dp[0][0] = 1;

for(i = 1; i <= 1024; i++)
    for(j = 0; j < 1024; j++) 
        // Check for overflow
        const int howmany = ways[i] * dp[i-1][j];
        dp[i][j] += howmany;
        dp[i][j^i] += howmany;
    


cout << (dp[1024][ans]);

对于计算odd_even_,您还可以使用以下内容:

nc0+nc2+... = nc1+nc3... = 2n-1

因为选择奇数的方法数=拒绝奇数的方法数=选择偶数的方法数

您还可以通过仅保留 2 列 dp 数组并在 dp[i-2][x] 被丢弃时重用它们来优化空间。

【讨论】:

我认为方式的数量较少。例如,如果输入为 4,1,1,1,目标为 5,则有效子集为 4,1 和 4,1,1,1。因此,选择奇数个 1 的方法数为(z+1)/2。选择偶数个 1 的方法有 (z+2)/2 谢谢@user3386109。我假设不同索引处的 1 计数不同。即4, 1可以3种方式选择,4, 1, 1, 1可以1种方式选择。 似乎问题中的代码将计数 4,1 三倍,但我不相信这是代码应该做的。我想这取决于 OP 来决定:) @MohitJain 你不觉得这个解决方案有问题吗? @ankitkumar 可能是。您能否详细说明可能出了什么问题。这个答案为实现最终解决方案提供了更多的方向/提示。每当我有额外的时间时,我都会编写一个测试用例来与 OP 的版本进行比较。【参考方案2】:

动态编程背后的理念是,(1) 永远不要两次计算相同的结果,(2) 只根据需要计算结果,而不是在你做的时候预先计算整个事情。

所以solve(arr, n, ans) 需要一个解决方案ans &lt; 1024n &lt; 1000000arr = array[n]。让dp[n][ans] 保存结果数量的想法是合理的,因此需要 dp 大小为dp = array[n+1][1024]。我们需要一种方法来区分尚未计算的结果和可用的结果。所以memset(dp, -1, sizeof(dp)) 然后就像你已经做过的dp[0][0] = 1

solve(arr, n, ans):
    if (dp[n][ans] == -1)
        if (n == 0) // and ans != 0 since that was initialized already
            dp[n][ans] = 0
        else
            // combine results with current and without current array element
            dp[n][ans] = solve(arr + 1, n - 1, ans) + solve(arr + 1, n - 1, ans XOR arr[0])
    return dp[n][ans]

优点是,您的 dp 数组只是在解决方案的途中部分计算,因此这可能会节省一些时间。

根据堆栈大小和n,可能需要将其从递归解决方案转换为迭代解决方案

【讨论】:

动态规划的大多数理论描述似乎都暗示了您的想法 (2)。但几乎所有动态编程的实际应用都抛弃了这一点。知道您需要哪些结果最终会大大增加解决方案的内存需求,而检测您已经计算过的结果(相对于以可预测的顺序计算)会大大增加所需的时间。 @JSF 是的,我不得不为此增加一点堆栈大小(正在工作),所以我尝试将其转换为某种工作队列解决方案,然后事情开始变得尴尬。所以我想一个好的解决方案真的需要处理一些细节,比如压缩重复输入数字的数量。

以上是关于给定一个长度为 n 的数组,找到子集的 XOR 等于给定数字的子集数的主要内容,如果未能解决你的问题,请参考以下文章

如何从给定数组中找到子集的最大和。 (子集不能没有大于任何两个元素之和的元素)

计算数组的最大长度,使得平均值小于给定值

计算给出数组中最小标准差的子集

使用递归c ++返回数组的子集

总和大于或等于 k ​​的最小子集

给定一个数组和一个总和,找到小于总和的最大长度连续子数组