【问题标题】:Minimal number of steps needed to turn all binary bits to one state将所有二进制位转换为一种状态所需的最少步骤数
【发布时间】:2011-01-21 05:20:39
【问题描述】:

有一个由 M 个二进制数组成的数组,每个二进制数都处于“0”或“1”状态。您可以执行几个步骤来更改数字的状态,并且在每个步骤中,您都可以更改恰好 N 个连续数字的状态。当然,给定数字 M、N 和包含成员的数组,您将要计算将所有二进制数转换为一种状态(1 或 0)所需的最小步骤数。


如果 M = 6 且 N = 3 且数组为 1 0 0 1 0 0,则解将为 2。 说明:我们可以分两步翻转它们,使它们都为 1:我们从索引 2 翻转到 4,然后将数组转换为 111000,然后将最后三个 (N) 翻转为 1。

【问题讨论】:

  • 1.这是作业吗? 2. M 和 N 有多大? (例如,O(M 2^N) 算法会起作用吗?) 3. 你的英语非常好。我不认为这个问题有一个著名的标题。
  • 问题是否保证总有解决方案?例如,对于 1 0 1 和 N = 3,没有解决方案。算法是否应该考虑这些情况?
  • 如果你尝试这个问题,在你的问题中输入一些代码或伪代码,然后请求 cmets,你可能会得到更多的响应。
  • M 和 N 在 1-100000 的范围内,因此暴力破解不会以任何可能的方式工作。这是一个动态规划问题,应该这样解决。
  • @thebretness:如果我有任何可能的想法,我不会在这里发帖——我会尝试一下,看看我得到了什么。问题是我不知道任何可能的方法来解决这个问题。 @Ivlad:你是对的,不会总是有解决方案,所以输出将是 -1 或 smth。

标签: c++ algorithm dynamic-programming


【解决方案1】:

如果我正确理解了这个问题,稍微思考一下就会让你相信,即使是动态编程也没有必要——解决方案完全是微不足道的。

这是我理解的问题:给你一个由 0 和 1 组成的数组 a[1]..a[M],并且允许你进行 Sk 形式的操作,其中 Sk 表示翻转 N 个元素 a[k],a[k+1],...a[k+N-1]。显然,这仅对 1≤k≤M-N+1 进行了定义。

通过执行一系列这些操作 Sk,您希望达到全 0 或全 1 的状态。我们将分别解决这两个问题,并取较小的数字。所以假设我们想让它们全为 0(另一种情况,全为 1,是对称的)。

关键的想法是你永远不想执行任何操作 Sk 超过一次(执行两次等同于根本不执行),并且操作顺序无关紧要。所以问题只是确定您执行的操作的哪个子集,这很容易确定。看[1]。如果它是 0,那么你知道你不会执行 S1。否则,你知道你必须执行 S1(因为这是唯一会翻转 a[1] 的操作),所以执行它——这会将所有位从 a[1] 切换到 a[N ]。现在看 a[2](在这个操作之后)。根据它是 1 还是 0,你知道你是否会执行 S2。等等——您可以确定在线性时间内执行多少操作(以及执行哪些操作)。

编辑: 将伪代码替换为 C++ 代码,因为有 C++ 标记。对不起丑陋的代码;当处于“比赛模式”时,我会恢复比赛习惯。 :-)

#include <iostream>
using namespace std;
const int INF = 20000000;
#define FOR(i,n) for(int i=0,_n=n; i<_n; ++i)

int flips(int a[], int M, int N, int want) {
  int s[M]; FOR(i,M) s[i] = 0;
  int sum=0, ans=0;
  FOR(i,M) {
    s[i] = (a[i]+sum)%2 != want;
    sum += s[i] - (i>=N-1?s[i-N+1]:0);
    ans += s[i];
    if(i>M-N and s[i]!=0) return INF;
  }
  return ans;
}

int main() {
  int M = 6, N = 3;
  int a[] = {1, 0, 0, 1, 0, 0};
  printf("Need %d flips to 0 and and %d flips to 1\n",
         flips(a, M, N, 0), flips(a, M, N, 1));
}

【讨论】:

  • 你能再次看到编辑过的问题吗?我发布了一个例子?解决方案对我来说看起来太容易了,这个问题应该真的很难(他们说)。
  • 哦,我刚刚意识到这个算法不是线性时间,因为“执行翻转”位也需要 N 中的线性时间,所以它变成了 O(MN)。您可以使用我相信的队列来解决这个问题(当前位置 N 内最后一次翻转位置的队列)。
  • @VaioIsBorn:我在示例中对其进行了测试,它发现了 2 个翻转。也许他们高估了它的硬度。 :-) @Poita_:看代码,而不是描述——您可以通过使用滑动窗口(即上面的“总和”)以恒定的时间跟踪每次翻转。
  • @VaiolsBorn:如果N 唯一的解决方案(当然,不是将同一组翻转两次),所以不知道在说什么“最低”要求的数字是。
  • @VaiolsBorn:上面的评论是全0。当然,如果我们可以选择全0或全1,那么有两种解决方案,我们只选择较小的一个。此外,该算法实际上证明了使所有 0 的唯一性(同样也可以证明所有 1)。
【解决方案2】:

我编写了 ShreevatsaR 提出的算法,但增加了队列改进以使其达到 M 中的真正线性时间。

int solve(vector<bool> bits, int N)
{
  queue<int> flips;
  int moves = 0;

  for (int i = 0; i < bits.size(); ++i)
  {
    if (!flips.empty() && flips.front() <= i - N)
      flips.pop();

    if ((bits[i] ^ (flips.size() % 2 == 0)) == 1)
    {
      if (i > bits.size() - N)
        return -1; // IMPOSSIBLE

      moves++;
      flips.push(i);
    } 
  }

  return moves;
}

只需在原件和倒置原件上运行它并取最小值(如果它们不是 -1)。如果两者都是-1,那么这是不可能的。

请注意,我没有编译或测试过该代码,但它应该可以工作。

【讨论】:

  • 是否有选择矢量的理由?大多数人反对它,当我看到它被使用时,我很难弄清楚实际发生了什么。尤其是 if 中使用的 operator^(bool, bool)。
  • (删除了之前的评论。)我实际上有线性时间的改进,但这是更好的(至少,更多的 C++-ish)代码。 +1。
猜你喜欢
  • 2019-07-15
  • 1970-01-01
  • 2018-02-24
  • 2012-11-26
  • 1970-01-01
  • 2016-05-30
  • 1970-01-01
  • 1970-01-01
  • 2022-01-21
相关资源
最近更新 更多