给定一个长度为 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 < 1024
、n < 1000000
和arr = 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 等于给定数字的子集数的主要内容,如果未能解决你的问题,请参考以下文章