【问题标题】:Minimum number of AND operations to make all array elements zero使所有数组元素为零的最小 AND 操作数
【发布时间】:2019-07-18 21:22:05
【问题描述】:

我在一次编程比赛中遇到了这个问题:

给定一个由 n 个元素组成的数组。在每次迭代中,您可以选择任意两个元素 aiaj 并替换 aiai & aj。 & 是按位与运算符。 找出使所有数组元素为零所需的最小 AND 操作数。

假设给定输入有一个解决方案。这个问题的最优解是什么?

编辑: 我已经为运行时间超过 1 秒的问题添加了我的 DP 解决方案。有什么加快速度的建议吗?

0

D:以 2 为底的最大位数(0

目标:2^D - 1

T[i][X] => {n_0, n_1, ..., n_i} 中使 X 为零的最小元素数

T[i][0] = 0

T[0][X>0] = INF

T[i][X] = min( 1 + T[i-1][X & n_i] , T[i-1][X] )

T[n][GOAL] -> AND 操作的最小次数

【问题讨论】:

  • "假设有一个解,这个问题的最优解是什么?"解决方案不会仅仅因为您认为它存在而实现:) 无论如何,您尝试了什么?
  • 你有什么想法?有什么线索吗?你用“动态编程”标记了这个问题,所以听起来你有一个想法。
  • 问题的那一部分要在问题的上下文中理解。比如数组{1,1}没有解,因为1 & 1 = 1,所以不能用上述操作清空数组。
  • 似乎缺少信息。在不知道数组中的值的情况下,您无法判断两个元素是否会产生零。
  • 这个编程竞赛还在进行吗?请发送一个链接。

标签: algorithm data-structures dynamic-programming greedy


【解决方案1】:

在我看来,这就像set cover problem。我们需要找到在每个位置都覆盖零的最小子集。一旦找到该子集,生成的“绝对”零可用于将其他元素转换为零。在下面的示例中,子集中的三个元素中的任何一个都可以用作第一个零。

1001
0101<
0011<
1110<
0111

【讨论】:

  • 不错的解决方案。那么最优性证明呢?
  • @AsafRosemarin 您是指在寻找答案时设置覆盖的最优性,还是覆盖所有零的最小子集的最优性,表示所需的最小移动次数?我希望后者或多或少是不言而喻的。
  • @AsafRosemarin 似乎不言自明。如果我们没有在子集中覆盖所有零位置,我们如何从该子集中使任何“绝对”零?
  • @Mbt925 设置封面是一个已知的研究问题。我会寻找解决二元情况的文献。但也许格雷戈里对这个问题有正确的解释?您确定允许我们检查数组元素吗?
  • @Mbt925 在现实世界中,使用整数程序求解器。在比赛中,也许分支和绑定就足够了。诚然,实现对偶单纯形法需要一段时间。
【解决方案2】:

如果问题对给定的输入有解决方案,您可以执行以下操作:

  1. 在 [0,n-1] 之间选择一个索引 i(假设数组索引是从零开始的)。

  2. 对于介于 0 和 n 之间且不是 i 的每个 j,执行 aii & aj。此时可以保证 a_i 等于 0,否则问题无法解决,因为您对数组中的所有项执行了位和。

  3. 对于介于 0 和 n 之间且不是 i 的每个 j,执行 aji & aj。这会对数组中的所有项执行 0,使它们也为 0。

第一个循环执行 n-1 次 and 操作,第二个循环执行 n-1 次,所以总共 2n-2 次 and 操作。

编辑:

这是假设您无法查看数组中的值。

【讨论】:

  • "找出使所有数组元素为零所需的最小 AND 操作数。"向我建议返回值必须是整数。您的解决方案不是最小的(并且不计算有多少 ANDs 甚至有效果)。如果您只需要将数组归零,则有更简单的按位组合元素的方法。
  • 您的解决方案提供了所需的最大 AND 操作数。
  • (我在您的答案中添加了一个空格,以便能够对其进行投票。我之前曾投反对票,但取决于我们如何解释问题,这可能是一个正确的答案。)
【解决方案3】:

我的猜测是,您可以通过使 DP 表稀疏来获得所需的加速。我们可以将生成的算法视为在节点为0..2^D-1 且边从xx&amp;y 的图上进行从2^D-10 的广度优先搜索,其中y 是数组元素。事实上,由于按位与的交换性/结合性,我们可以通过要求x&amp;y 清除x 中的最低位集来收紧边集。在下面的 Python 代码中,通过使用映射 zero_index 可以有效地实现这一点,但在 C 中我会使用数组(并根据需要将集合替换为位图或数组)。

import collections
import random


def min_and(seq):
    lst = list(seq)
    zero_index = collections.defaultdict(lambda: set(lst))
    for x in lst:
        y = x
        while y:
            zero_index[y & ~(y - 1)].discard(x)
            y &= y - 1
    visited = set()
    fringe = set(lst)
    i = 0
    while 0 not in fringe:
        visited.update(fringe)
        fringe = {
            x & y
            for x in fringe for y in zero_index[x & ~(x - 1)]
            if x & y not in visited
        }
        i += 1
    return i + len(lst) - 1


print(min_and(
    random.randrange(2**18) | random.randrange(2**18) | random.randrange(2**18)
    for i in range(100)))

【讨论】:

  • 感谢您的解决方案。有用。你能解释更多关于y &amp; ~(y - 1)零件和收紧边缘的信息吗?为什么要清除最低位?
  • @Mbt925 y &amp; ~(y - 1) 是提取最低设置位的习语。我们知道在按位 AND 中必须有一个该位清晰的数字,因此通过强制它成为图中的第一步,我们减少了必须考虑的中间状态的数量。
  • 我错误地将赏金给了另一个用户。对不起。
  • @Mbt925 嗯,我有足够的代表。
  • @Mbt925 在 C++ 中工作,我会将 zero_index 设为向量的向量,并使用内部函数来查找第一个零的索引(或在 ~x 中查找第一个)以查找-UPS。我会将visited 设为位图,将fringe 设为矢量,但会急切地更新visited 以防止fringe 中的重复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-29
  • 2019-05-27
  • 1970-01-01
  • 2015-11-09
相关资源
最近更新 更多