如何解决在 XOR 序列上最大化 AND
问题
我们得到了两个长度为 foselastica
的数组 a
和 b
。我们通过重新排列 n
中的值来构建第三个数组 c
。目标是找到最大化
b
c
其中 result = (a[0] ^ c[0]) & (a[1] ^ c[1]) & ... & (a[n - 1] ^ c[n - 1])
是 XOR 而 ^
是 AND。
是否可以有效地执行此操作?迭代 &
的所有可能排列很简单,但这对于大 b
来说是不可行的。
更多详情
-
n
中值的顺序是固定的。 -
a
中的值的顺序可以重新排列以形成b
。即以c
开头,可能是将值重新排列为b = [1,2,3]
时得到的结果最大。
如果需要, -
c = [2,1,3]
可以就地重新排列(我在示例代码中这样做了)。 - 由于最优
b
不一定唯一,因此可能返回任何最优c
。 - 假设所有值都是 32 位无符号整数。
-
c
。
测试用例
1 <= n <= 10,000
Input:
a = [3,4,5]
b = [6,7,8]
Output:
c = [8,6] (result = 3)
Input:
a = [1,11,10,11]
b = [6,20,8,9,7]
Output:
c = [8,6,20] (result = 9)
示例代码
这是我用于一个天真的解决方案的 C++ 代码,该代码详尽地尝试了 Input:
a = [0,16]
b = [512,256,128,64,32,16]
Output:
c = [16,512] (result = 0)
的所有排列(使用 Visual Studio 2019 在 Windows 10 上测试):
b
解决方法
狄龙的回答已经有了最重要的想法。使用这些想法,我们可以在线性时间和空间中解决问题。
关键目标是使结果 1
的高有效位,无论我们是否牺牲较低的有效位。如果我们专注于单个位 k
,那么我们可以执行以下操作:
- 将
a
中第k
位设置为1
(a1
) 和0
(a0
) 的数字进行分区,分别 - 将
b
中第k
位设置为1
(b1
) 和0
(b0
) 的数字进行分区,分别 - 如果
a1
中的条目数等于b0
中的条目数,我们可以将结果中的第k
位设置为1
。在这种情况下,我们认为分区成功。
分区的含义如下:在我们最终的c
中,我们需要将a1
的条目与b0
的条目以及a0
的条目匹配到b1
。如果我们这样做,XOR 运算将对所有条目产生 1
,AND 运算将产生整体 1
。
不,我们如何在算法中使用这种洞察力?
我选择用索引来表示 a
的分区(即,分区是一组索引的集合)和 b
用实际数字的分区。最初,我们从每个只有一个集合的分区开始(即,a
的分区具有所有索引的集合,而 b
的分区具有 b
作为元素)。
我们从最重要的位开始,并尝试进行分区。
如果分区成功,我们最终会为 a
和 b
生成两个分区(其中一个可能是空的)。然后我们已经知道 b
中的哪些数字可以放在哪些索引上。如果我们违反了这个结果,我们会得到一个较小的最终结果。
如果我们的分区不成功,我们只需忽略这一步。
现在让我们继续下一点。我们可能已经有一个分区,它不仅有初始集,还有更细粒度的东西。我们不想混淆分区。因此,我们可以使用与以前相同的方法对分区进行分区。如果我们对所有分区都成功,我们从现在开始使用子分区。如果没有,我们使用原始分区。
如果我们对所有位都这样做,我们最终会得到 b
中数字的映射以及它们可以放入的索引以获得最大的最终结果。它可能不是唯一的映射。如果一个分区包含多个元素,任何映射都会产生最大值。所以我们只需要选择一个就可以得到结果。
这是您问题中的一个示例:
a = { 1,11,7,4,10,11 }
= { 0001b,1011b,0111b,0100b,1010b,1011b }
b = { 6,20,8,9,7 }
= { 0110b,10100b,1000b,1001b,0111b }
这里是最重要的算法步骤:
index partitioning | b partitioning
-----------+------------------------+-----------------------
initial | { 0,1,2,3,5 } | {6,7 }
------------+------------------------+-----------------------
after bit 3 | { 1,5 } | { 6,7 }
| { 0,3 } | { 8,10 }
------------+------------------------+-----------------------
after bit 0 | { 1,5 } | { 6,20 }
(final) | { 4 } | { 7 }
| { 0,2 } | { 8,10 }
| { 3 } | { 9 }
所以我们有一个非唯一案例。数字 6
和 20
可以同时出现在索引 1
和 5
处。但是编号 7
肯定会出现在索引 4
处。一种解决方案是:
c = { 8,6,20 }
检查:
a = { 0001b,1011b }
XOR
c = { 1000b,0110b,10100b }
-------------------------------------------------
{ 1001b,1101b,11111b }
AND = 1001b = 9
这是一些示例 C++ 代码。请注意,代码的重点是可理解性。有一些事情可以更有效地实施。
#include <iostream>
#include <vector>
#include <cstdint>
struct Partition
{
std::vector<size_t> indices;
std::vector<uint32_t> bs;
};
struct Partitioning
{
bool success;
Partition p1;
Partition p2;
};
Partitioning partition(const std::vector<uint32_t>& a,const std::vector<size_t>& indices,const std::vector<uint32_t>& b,size_t bit)
{
uint32_t mask = 1 << bit;
Partitioning result;
// partition the indices of a
for (size_t i : indices)
{
uint32_t n = a[i];
if (n & mask)
result.p1.indices.push_back(i);
else
result.p2.indices.push_back(i);
}
// partition b
for (uint32_t n : b)
if (n & mask)
result.p2.bs.push_back(n);
else
result.p1.bs.push_back(n);
// check if we are successful
bool canMakeBit1 = result.p1.indices.size() == result.p1.bs.size();
result.success = canMakeBit1;
return result;
}
void findMax(const std::vector<uint32_t>& a,const std::vector<uint32_t>& b)
{
std::vector<uint32_t> aIndices(a.size());
for (size_t i = 0; i < a.size(); ++i)
aIndices[i] = i;
// current partitioning
std::vector<std::vector<uint32_t>> partsIndices;
partsIndices.push_back(aIndices);
std::vector<std::vector<uint32_t>> partsBs;
partsBs.push_back(b);
// temporary partitionings
std::vector<Partitioning> partitionings;
// assume 32 bits
size_t bit = 32;
do
{
--bit;
bool success = true;
partitionings.clear();
// try to partition all current partitions
for (size_t i = 0; i < partsIndices.size(); ++i)
{
partitionings.push_back(partition(a,partsIndices[i],partsBs[i],bit));
if (!partitionings.back().success)
{
success = false;
break;
}
}
// if all partitionings are successful
if (success)
{
// replace the current partitioning with the new one
partsIndices.clear();
partsBs.clear();
for (auto& p : partitionings)
{
if (p.p1.indices.size() > 0)
{
partsIndices.push_back(p.p1.indices);
partsBs.push_back(p.p1.bs);
}
if (p.p2.indices.size() > 0)
{
partsIndices.push_back(p.p2.indices);
partsBs.push_back(p.p2.bs);
}
}
}
} while (bit > 0);
// Generate c
std::vector<uint32_t> c(a.size());
for (size_t i = 0; i < partsIndices.size(); ++i)
{
const auto& indices = partsIndices[i];
const auto& bs = partsBs[i];
for (size_t j = 0; j < indices.size(); ++j)
{
c[indices[j]] = bs[j];
}
}
// Print the result
uint32_t result = 0xffffffff;
for (size_t i = 0; i < a.size(); ++i)
{
std::cout << c[i] << " ";
result = result & (a[i] ^ c[i]);
}
std::cout << std::endl << result << std::endl;
}
int main()
{
std::vector<uint32_t> a = { 1,11 };
std::vector<uint32_t> b = { 6,7 };
findMax(a,b);
return 0;
}
,
TL;DR
我认为可以将其重新表述为 assignment problem,其最优解可以在 O(n^3) 时间内找到。但我没有在我的回答中尝试这样做。
免责声明
我将描述的方法仍然涉及检查排列。它通常似乎比天真的方法需要更少的迭代,但我的解决方案的额外开销实际上可能会降低整体速度。我没有将我的方法的运行时间与天真的方法进行比较,也没有彻底检查它是否没有错误(对于提供的 3 个测试用例,它似乎工作正常)。
也就是说,在此过程中我确实有一些见解,所以也许我在这方面的尝试将帮助其他人提出更有效的解决方案。希望我的解释是清楚的,而且我不是在胡说八道。
总体思路
假设我们创建了一个无向的 bipartite graph,其中两个独立的节点集分别对应于 a
和 b
,每个节点都是一个数组元素。
让我们一次考虑一位,例如最低有效位 (LSB)。 我们将 LSB 标记为位 0。 我们还考虑第一个测试用例(为了简单起见,我们只考虑最低的 4 位而不是全部 32 位):
Input:
a = [3,5] // binary: [0011,0100,0101]
b = [6,8] // binary: [0110,0111,1000]
我们的图在 a
集合中有 3 个节点,分别标记为 3、4 和 5;并且它在 b
集合中有 3 个节点,分别标记为 6、7 和 8。如果 a
节点的所选位(位 0)不同于b
节点。例如,我们将在节点 3 和 6 之间绘制一条边,因为 3 (0011) 的 LSB 与 6 (0110) 的 LSB 不同。我们不会在节点 3 和 7 之间绘制边,因为 3 (0011) 的 LSB 与 7 (0111) 的 LSB 相同。如果我们一路解决这个问题,我们最终会得到以下位 0 的邻接表:
3: 6,8
4: 7
5: 6,8
我们有两组边可以从 a
中的每个节点组成:
- 如果所选位为 1,则为
b
中所选位为 0 的所有节点绘制一条边。 - 如果所选位为 0,则为
b
中所选位为 1 的所有节点绘制一条边。
我们可以观察到,当且仅当该位为 1 的 a
节点的数量与 {{1}位为 0 的节点。否则,该位在最终结果中不能为 1,因为我们必须将至少一个 b
节点与一个相同位值的 a
节点配对,在 XOR 之后为该位生成 0,因此在 AND 之后为该位生成 0。
现在,如果我们为 4 位中的每一个创建这样的图,并确定哪些位是最终结果中的 1 的候选者,这将为我们提供最佳结果的上限。这也为我们提供了一个明显的结果下限,因为我们知道我们至少可以找到 b
的排序,导致设置最重要的候选位。
下面给出了我们测试用例中每个位的邻接表。
b
寻找Bit 0 (LSB)
3: 6,8
(candidate bit)
Bit 1
3: 8
4: 6,7
5: 6,7
(candidate bit)
Bit 2
3: 6,7
4: 8
5: 8
(NOT candidate)
Bit 3 (MSB)
3: 8
4: 8
5: 8
(NOT candidate)
Upper bound: 3 (both candidate bits 0 and 1 are set)
Lower bound: 2 (only candidate bit 1 is set)
为了得到最优的 c
,我们需要从最重要的候选位开始。我们循环遍历所有有效的 c
(c
的排列,我们遵守该位的邻接列表),与 {{ 的可能排列总数相比,其中希望相对较少1}}。
对于这些排列中的每一个,我们查看它们中的任何一个对于下一个最重要的候选位是否有效。如果它们都不是,那么我们检查之后的位,依此类推。如果在遵守 MSB 上的任何排列的邻接列表时不能包含其他位,那么最终结果中只有 MSB 可以为 1,这就是解决方案。否则,我们希望优先处理对更重要的位有效的排列,并且我们需要递归地检查在该点之前对所有位有效的排列。
排列“有效”是什么意思?本质上,邻接表充当从 b
到 b
的映射的约束。如果一个候选位的约束与另一个候选位的约束不冲突,我们知道这些位在最终结果中都可以是 1。例如,查看位 0 和 1,我们看到有一种方法可以同时满足这两个映射(即 a
)。因此,该排列 (c
) 对位 0 和 1 均有效。(事实上,该排列被证明是最佳排列。)
对于冲突约束的示例,考虑位 0 和 2。位 2 要求节点 4 连接到节点 8。也就是说,在满足 3: 8; 4: 7; 5: 6
的索引 c = [8,6]
处,我们需要设置i
以便在最终结果中设置位 2。但是,位 0 要求节点 4 连接到节点 7。这是一个冲突,因此在最终结果中不能同时设置位 0 和 2。 (但无论如何,位 2 都不是候选位,因为 a[i] == 4
节点中的两个(4 和 5)对该位具有 1 但只有一个 c[i] = 8
节点(8)具有 0为了那一点。)
与已解决的图论问题的可能关系
我对图论问题不是很熟悉,但在我看来,这个问题的二部图公式与 maximum weighted bipartite matching 也称为 assignment problem 相关。不过,我们的边缘目前没有加权。也许一条边可以根据它存在多少位来加权?也许更重要的位会被赋予更大的权重?我认为我们仍然只需要考虑候选位的图。
本质上,从每个候选位的二部图构建一个 a
xb
邻接矩阵。为边分配统一权重 n
;所以边缘权重是位 0 为 1,位 1 为 2,位 2 为 4,依此类推。这确保了更重要的候选位优先于较小位的任何组合。然后组合所有邻接矩阵的权重以构建最终的代表性邻接矩阵。这将是分配问题的输入。
如果此比较有效,则存在 Hungarian algorithm 以在 O(n^3) 时间内最优解决分配问题。这明显优于朴素排列方法所需的 O(n!) 时间。
乱码
为了完整起见,我将我为我提议的方法编写的代码包括在内,尽管我认为将这个问题重新表述为赋值问题的可能性更值得研究。要使用此代码,请从问题中提供的示例代码开始,然后将此代码复制到同一文件中。在 n
中,调用 pow(2,bit)
而不是 main
。
solvePermuteLess
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。