【问题标题】:Looping over a bitmask循环位掩码
【发布时间】:2016-12-08 07:22:35
【问题描述】:

这是对TopCoder SRM 466 "Lottery Ticket" problem 的提交。我已经看到这种模式多次用于这个问题。

尼克喜欢玩彩票。一张彩票的成本就是价格。尼克正好有四张钞票,其值为b1b2b3b4(其中一些值可能相等)。他想知道是否可以在不取回任何零钱的情况下购买一张彩票。换句话说,他想用他的纸币的任何子集支付一张票的确切价格。如果可能,则返回“POSSIBLE”,否则返回“IMPOSSIBLE”(为清楚起见,所有引号)。

string buy(int p, int b1, int b2, int b3, int b4) {
    int arr[] = {b1, b2, b3, b4};
    for (int msk = 0; msk < (1 << 4); ++msk) {
        int sum = 0;
        for (int i = 0; i < 4; ++i) {
            if (msk & (1 << i)) {
                sum += arr[i];
            }
        }
        if (sum == p) return "POSSIBLE";
    }
    return "IMPOSSIBLE";
}

有人能解释一下这是如何工作的吗?我不明白他为什么将值放入一个数组并使用两个嵌套的 for 循环进行循环。

【问题讨论】:

    标签: algorithm bitmask


    【解决方案1】:

    这个问题可以扩展到任意数量的钞票,但让我们看一下这个例子。

    此解决方案的想法是使用蛮力方法来解决问题。这意味着我会尝试所有可能的解决方案,如果其中一个有效,那么结果是肯定的。

    在这种情况下可行的解决方案意味着我挑选的钞票的总和等于p

    我们先来看这段代码:

    for (int msk = 0; msk < (1 << 4); ++msk)
    

    这表示我将遍历从02^4-1 的所有数字,即0-15

    如果你用它们的二进制符号来写这些数字,你会注意到它们涵盖了长度为 4 的所有可能组合(我们不必写所有前导零,但对于类型 @987654327,它实际上总共有 32 位@)。

    0000
    0001
    0010
    0011
    0100
    0101
    0110
    0111
    1000
    1001
    1010
    1011
    1100
    1101
    1110
    1111
    

    让我们选择其中一个示例,例如1010。这意味着我将在13 位置选择数字(从右到左从0开始)。然后我会检查这两个数字的和是否等于p

    下一个 for 循环将所有具有1 的位置的数字相加:

    for (int i = 0; i < 4; ++i) {
        if (msk & (1 << i)) {
            sum += arr[i];
        }
    }
    

    如果我们分解它,那么我们有 msk 代表我们正在检查的当前组合和 (1 &lt;&lt; i) 这只是向我们提供2^i 的左移按位运算,或者以二进制表示:

    0001 = 1 << 0
    0010 = 1 << 1
    0100 = 1 << 2
    1000 = 1 << 3
    

    注意:(1 &lt;&lt; i) 在括号内,因为&amp; 具有更高的优先级,在这种情况下我们不希望这样。

    如果你在两个整数之间使用&amp;运算符,你会得到按位运算,例如

    1010 & 1000 = 1000   // this is greater than 0
    1010 & 0100 = 0000   // this is equal to 0
    

    因此,if (msk &amp; (1 &lt;&lt; i)) 仅适用于当前组合中具有1 的职位,即msk

    我希望这也解释了他将值放入数组中的原因 - 这是因为他想为每张钞票分配一个索引,然后如果掩码的位置有 1 则使用该钞票,而不是弄清楚应该使用 4 个变量中的哪一个。

    【讨论】:

    • 感谢您的全面回答。我现在完全有信心理解它。另一个回答者告诉我这是一个指数算法。考虑到可能有数千个值,您知道任何更有效的方法吗?
    • 确实如此。这具有指数复杂性,因为您正在尝试所有可能性并且总共有 2^n。实际复杂度为 O(2^n * n),其中 n 是钞票数量。这可以使用动态规划以更好的方式解决。这是en.wikipedia.org/wiki/Knapsack_problem 的子问题。在这种情况下,每个项目的价值(成本)将为 1,您只对权重感兴趣。先看一下背包算法,如果你不明白如何解决它,那么我可以更详细地解释它。复杂度还是不是很好O(P*N)
    • 精彩的解释!非常感谢!
    【解决方案2】:

    它只生成所有 16 种可能的组合。每个组合用 4 位表示,1 表示已使用,0 表示未使用。

    然后它计算组合的总和,如果总和正确,则打印“可能”。

    【讨论】:

      【解决方案3】:

      对于每张纸币,您有两种选择,接受或离开(开或关)。 使用 4 个音符,您可以将它们视为 4 位,如果您以二进制形式遍历 0000 到 1111,则可以遍历所有可能的选择组合。

      这就是位向量的作用。外层循环生成所有可能的子集,内层循环评估一个子集,看它是否匹配所需的总和。

      【讨论】:

      • 谢谢。你知道这个算法的时间复杂度是多少吗?
      • 就纸币数量而言,它是一种指数算法。 O(2^n) 其中 n 是音符的数量,在这种情况下为 4。
      猜你喜欢
      • 2017-10-22
      • 2014-11-19
      • 1970-01-01
      • 2018-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多