将所有二进制位转换为一个状态所需的最少步骤数

时间:2010-02-25 21:34:46

标签: c++ algorithm dynamic-programming

有一个M二进制数组,每个都处于状态“0”或“1”。您可以在更改数字状态时执行几个步骤,并且在每个步骤中您都可以更改N个连续数字的状态。给定数字M,N和带有成员的数组,您将要计算将所有二进制数转换为一个状态所需的最小步数 - 1或0.


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

2 个答案:

答案 0 :(得分:6)

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

这是我理解的问题:给你一个数组a [1] .. a [M]的0和1,你可以操作形式S k ,其中S k 意味着翻转N个元素a [k],a [k + 1],... a [k + N-1]。这仅限于1≤k≤M-N + 1,显然。

通过执行这些操作S k 的序列,您希望达到全0或全1的状态。我们将分别解决两个问题,并采用较小的数字。因此,假设我们想要将它们全部设为0(另一种情况,所有1都是对称的)。

至关重要的想法是你永远不会想要多次执行任何操作S k (做两次相当于完全不做),以及操作顺序无关紧要。所以问题只是确定你执行的操作的哪个子集,这很容易确定。看一下[1]。如果它为0,那么你知道你不会执行S 1 。否则,你知道你必须执行S 1 (因为这是翻转[1]的唯一操作),所以执行它 - 这将把所有位从[1]切换到[N] ]。现在看一下[2](在此操作之后)。根据它是1还是0,您知道是否将执行S 2 。依此类推 - 您可以在线性时间内确定要执行的操作数(以及执行数)。

编辑:用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));
}

答案 1 :(得分:3)

我编写了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则不可能。

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