通过翻转单元格组来用零填充二维数组

Fill 2-dimensional array with zeros by flipping groups of cells

本文关键字:填充 二维数组 翻转 单元格      更新时间:2023-10-16

有一个问题,我需要用零填充数组,有以下假设:

  • 数组中只能有01
  • 我们只能将0改为1, 1改为0
  • 当我们在数组中遇到1时,我们必须将其更改为0,这样它的邻居也会更改,例如,对于像下面这样的数组:
1 0 1
1 1 1
0 1 0

当我们改变(1,1)处的元素时,我们得到了这样的数组:

1 1 1
0 0 0
0 0 0
  • 我们不能改变第一行
  • 我们只能改变数组
  • 中的元素
  • 最终结果是我们必须将1更改为0以将数组归零的次数
第一个例子,数组如下所示:
0 1 0
1 1 1
0 1 0

答案是1.

第二个例子,数组如下:
0 1 0 0 0 0 0 0
1 1 1 0 1 0 1 0
0 0 1 1 0 1 1 1
1 1 0 1 1 1 0 0
1 0 1 1 1 0 1 0
0 1 0 1 0 1 0 0

答案是10。

也可能是不可能将数组归零的情况,那么答案应该是"不可能"。

不知何故,我不能得到这个工作:对于第一个例子,我得到了正确的答案(1),但对于第二个例子,程序说impossible而不是10。

你知道我的代码有什么问题吗?

#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
    int n,m;
    cin >> n >> m;
    bool tab[n][m];
    for(int i=0; i<n; i++)
        for(int j=0; j<m; j++)
            cin >> tab[i][j];
    int counter = 0;
    for(int i=0; i<n-1; i++)
    {
        for(int j=0; j<m-1; j++)
        {
            if(tab[i][j] == 1 && i > 0 && j > 0)
            {
                tab[i-1][j] = !tab[i-1][j];
                tab[i+1][j] = !tab[i+1][j];
                tab[i][j+1] = !tab[i][j+1];
                tab[i][j-1] = !tab[i][j-1];
                tab[i][j] = !tab[i][j];
                counter ++;
            }
        }
    }
    bool impossible = 0;
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<m; j++)
        {
            if(tab[i][j] == 1)
            {
                cout << "impossiblen";
                impossible = 1;
                break;
            }
        }
        if(impossible)
            break;
    }
    if(!impossible)
        cout << counter << "n";
    return 0;
}

我认为您的程序在6x8矩阵中返回不可能的的原因是您一直以从左到右/从上到下的方式遍历,将遇到的每个1实例替换为0。虽然这看起来似乎是正确的解决方案,但它所做的只是通过修改矩阵的相邻值来分散矩阵周围的1和0。我认为解决这个问题的方法是从下到上/从右到左把1推到第一行。在某种程度上把他们逼入绝境,直到他们被消灭。

无论如何,这是我对这个问题的解决方案。我不太确定这是不是你想要的,但我认为它可以满足你提供的三个矩阵的要求。代码不是很复杂,最好用一些更难的问题来测试它,看看它是否真的有效。
#include <iostream>
static unsigned counter = 0;
template<std::size_t M, std::size_t N>
void print( const bool (&mat) [M][N] )
{
    for (std::size_t i = 0; i < M; ++i)
    {
        for (std::size_t j = 0; j < N; ++j)
            std::cout<< mat[i][j] << " ";
        std::cout<<std::endl;
    }
    std::cout<<std::endl;
}
template<std::size_t M, std::size_t N>
void flipNeighbours( bool (&mat) [M][N], unsigned i, unsigned j )
{
    mat[i][j-1] = !(mat[i][j-1]);
    mat[i][j+1] = !(mat[i][j+1]); 
    mat[i-1][j] = !(mat[i-1][j]); 
    mat[i+1][j] = !(mat[i+1][j]); 
    mat[i][j]   = !(mat[i][j]);
    ++counter;
}
template<std::size_t M, std::size_t N>
bool checkCornersForOnes( const bool (&mat) [M][N] )
{
    return (mat[0][0] || mat[0][N-1] || mat[M-1][0] || mat[M-1][N-1]);
}
template<std::size_t M, std::size_t N>
bool isBottomTrue( bool (&mat) [M][N], unsigned i, unsigned j )
{
    return (mat[i+1][j]);
}
template<std::size_t M, std::size_t N>
bool traverse( bool (&mat) [M][N] )
{
    if (checkCornersForOnes(mat))
    {
        std::cout<< "-Found 1s in the matrix corners." <<std::endl;
        return false;
    }
    for (std::size_t i = M-2; i > 0; --i)
        for (std::size_t j = N-2; j > 0; --j)
            if (isBottomTrue(mat,i,j))
                flipNeighbours(mat,i,j);
    std::size_t count_after_traversing = 0;
    for (std::size_t i = 0; i < M; ++i)
        for (std::size_t j = 0; j < N; ++j)
            count_after_traversing += mat[i][j];
    if (count_after_traversing > 0)
    {
        std::cout<< "-Found <"<<count_after_traversing<< "> 1s in the matrix." <<std::endl;
        return false;
    }
    return true;
}

#define MATRIX matrix4
int main()
{
    bool matrix1[3][3] = {{1,0,1},
                         {1,1,1},
                         {0,1,0}};
    bool matrix2[3][3] = {{0,1,0},
                         {1,1,1},
                         {0,1,0}};
    bool matrix3[5][4] = {{0,1,0,0},
                         {1,0,1,0},
                         {1,1,0,1},
                         {1,1,1,0},
                         {0,1,1,0}};
    bool matrix4[6][8] = {{0,1,0,0,0,0,0,0},
                         {1,1,1,0,1,0,1,0},
                         {0,0,1,1,0,1,1,1},
                         {1,1,0,1,1,1,0,0},
                         {1,0,1,1,1,0,1,0},
                         {0,1,0,1,0,1,0,0}};

    std::cout<< "-Problem-" <<std::endl;
    print(MATRIX);
    if (traverse( MATRIX ) )
    {
        std::cout<< "-Answer-"<<std::endl;
        print(MATRIX);
        std::cout<< "Num of flips = "<<counter <<std::endl;
    }
    else
    {
        std::cout<< "-The Solution is impossible-"<<std::endl;
        print(MATRIX);
    }
}

matrix1:

-Problem-
1 0 1 
1 1 1 
0 1 0 
-Found 1s in the matrix corners.
-The Solution is impossible-
1 0 1 
1 1 1 
0 1 0 

matrix2:

-Problem-
0 1 0 
1 1 1 
0 1 0 
-Answer-
0 0 0 
0 0 0 
0 0 0 
Num of flips = 1

matrix3:

-Problem-
0 1 0 0 
1 0 1 0 
1 1 0 1 
1 1 1 0 
0 1 1 0 
-Found <6> 1s in the matrix.
-The Solution is impossible-
0 1 1 0 
1 0 1 1 
0 0 0 0 
0 0 0 1 
0 0 0 0 
matrix4的输出(它解决了您的原始问题):
-Problem-
0 1 0 0 0 0 0 0 
1 1 1 0 1 0 1 0 
0 0 1 1 0 1 1 1 
1 1 0 1 1 1 0 0 
1 0 1 1 1 0 1 0 
0 1 0 1 0 1 0 0 
-Answer-
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
Num of flips = 10

好吧,这是我的一些不同的尝试。

<<p> 想法/strong>

注意:我假设这里的"我们不能改变第一行"意味着"我们不能改变最外面的行"。

一些术语:

  • 切换一点,我的意思是改变它的值从0到1或1到0
  • 翻转一点,我的意思是切换该位和它周围的4位

切换位的行为是可交换。也就是说,不管我们按什么顺序切换它——最终结果总是相同的(这是一个微不足道的语句)。这意味着翻转也是一种交换作用,我们可以自由地按我们喜欢的顺序翻转比特。

在矩阵边缘上切换值的唯一方法是通过翻转它旁边的位不均匀的次数。因为我们在寻找次数最少的抛掷,我们希望抛掷的次数最多为1次。因此,在下面的场景中,x将需要恰好翻转一次,y将需要恰好翻转0次。

. .
1 x
0 y
. ,

由此我们可以得出两个结论:

  1. 矩阵的一个角永远不能被切换——如果在角上找到一个1,那么无论翻转多少次都不可能使矩阵为零。因此,您的第一个示例可以在不翻转单个位的情况下被丢弃。

  2. 角旁边的位必须与另一边的位具有相同的值。因此,您在评论中发布的这个矩阵也可以在不翻转单个位(右下角)的情况下被丢弃。

以上条件的两个例子:

0 1 .
0 x .
. . .

不可能,因为x需要恰好翻转一次正好零次。

0 1 .
1 x .
. . . 

可能,x只需要翻转一次。


现在可以创建一个递归参数,我建议如下:

  1. 我们得到一个m × n矩阵。
  2. 检查如上所述的角条件(即角!= 1,角旁边的位必须是相同的值)。如果违反任何一个条件,返回impossible
  3. 沿着矩阵的边缘移动。如果遇到1,则翻转最接近的位,并在计数器上加1。
  4. 现在从#1重新开始,m - 2 × n - 2矩阵(顶部和底部行被删除,左列和右列)如果任何一个维度> 2,否则打印计数器并退出。

实施

一开始我认为这会变得很漂亮,但事实上它比我最初想象的要麻烦一些,因为我们必须跟踪很多指标。如果你对实现有疑问,请提问,但它本质上是对上述步骤的纯粹翻译。

#include <iostream>
#include <vector>
using Matrix = std::vector<std::vector<int>>;
void flip_bit(Matrix& mat, int i, int j, int& counter)
{
    mat[i][j] = !mat[i][j];
    mat[i - 1][j] = !mat[i - 1][j];
    mat[i + 1][j] = !mat[i + 1][j];
    mat[i][j - 1] = !mat[i][j - 1];
    mat[i][j + 1] = !mat[i][j + 1];
    ++counter;
}
int flip(Matrix& mat, int n, int m, int p = 0, int counter = 0)
{
    // I use p for 'padding', i.e. 0 means the full array, 1 means the outmost edge taken away, 2 the 2 most outmost edges, etc.
    // max indices of the sub-array
    int np = n - p - 1; 
    int mp = m - p - 1;
    // Checking corners
    if (mat[p][p] || mat[np][p] || mat[p][mp] || mat[np][mp] || // condition #1
        (mat[p + 1][p] != mat[p][p + 1]) || (mat[np - 1][p] != mat[np][p + 1]) || // condition #2
        (mat[p + 1][mp] != mat[p][mp - 1]) || (mat[np - 1][mp] != mat[np][mp - 1]))
        return -1;
    // We walk over all edge values that are *not* corners and
    // flipping the bit that are *inside* the current bit if it's 1
    for (int j = p + 1; j < mp; ++j) {
        if (mat[p][j])  flip_bit(mat, p + 1, j, counter);
        if (mat[np][j]) flip_bit(mat, np - 1, j, counter);
    }
    for (int i = p + 1; i < np; ++i) {
        if (mat[i][p])  flip_bit(mat, i, p + 1, counter); 
        if (mat[i][mp]) flip_bit(mat, i, mp - 1, counter);
    }
    // Finished or flip the next sub-array?
    if (np == 1 || mp == 1)
        return counter;
    else 
        return flip(mat, n, m, p + 1, counter);
}
int main()
{
    int n, m;
    std::cin >> n >> m;
    Matrix mat(n, std::vector<int>(m, 0));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            std::cin >> mat[i][j];
        }
    }
    int counter = flip(mat, n, m);
    if (counter < 0)
        std::cout << "impossible" << std::endl;
    else 
        std::cout << counter << std::endl;
}

3 3
1 0 1
1 1 1
0 1 0

不可能
3 3
0 1 0
1 1 1
0 1 0
1

6 8
0 1 0 0 0 0 0 0 
1 1 1 0 1 0 1 0 
0 0 1 1 0 1 1 1 
1 1 0 1 1 1 0 0 
1 0 1 1 1 0 1 0 
0 1 0 1 0 1 0 0 
10

4 6
0 1 0 0
1 0 1 0
1 1 0 1
1 1 1 0 
1 1 1 0

不可能

如果tab[0][j]为1,则必须切换tab[1][j]以清除它。如果不清除第0行,就无法切换第1行。这看起来像是一个还原步骤。重复这个步骤,直到剩下一行。如果最后一行运气不好,我的直觉是这是"不可能"的情况。

#include <memory>
template <typename Elem>
class Arr_2d
  {
  public:
    Arr_2d(unsigned r, unsigned c)
      : rows_(r), columns_(c), data(new Elem[rows_ * columns_]) { }
    Elem * operator [] (unsigned row_idx)
      { return(data.get() + (row_idx * columns_)); }
    unsigned rows() const { return(rows_); }
    unsigned columns() const { return(columns_); }
  private:
    const unsigned rows_, columns_;
    std::unique_ptr<Elem []> data;
  };
inline void toggle_one(bool &b) { b = !b; }
void toggle(Arr_2d<bool> &tab, unsigned row, unsigned column)
  {
    toggle_one(tab[row][column]);
    if (column > 0)
      toggle_one(tab[row][column - 1]);
    if (row > 0)
      toggle_one(tab[row - 1][column]);
    if (column < (tab.columns() - 1))
      toggle_one(tab[row][column + 1]);
    if (row < (tab.rows() - 1))
      toggle_one(tab[row + 1][column]);
  }
int solve(Arr_2d<bool> &tab)
  {
    int count = 0;
    unsigned i = 0;
    for ( ; i < (tab.rows() - 1); ++i)
      for (unsigned j = 0; j < tab.columns(); ++j)
        if (tab[i][j])
          {
            toggle(tab, i + 1, j);
            ++count;
          }
    for (unsigned j = 0; j < tab.columns(); ++j)
      if (tab[i][j])
        // Impossible.
        return(-count);
    return(count);
  }
unsigned ex1[] = {
0, 1, 0,
1, 1, 1,
0, 1, 0
};
unsigned ex2[] = {
0, 1, 0, 0, 0, 0, 0, 0,
1, 1, 1, 0, 1, 0, 1, 0,
0, 0, 1, 1, 0, 1, 1, 1,
1, 1, 0, 1, 1, 1, 0, 0,
1, 0, 1, 1, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 0
};
Arr_2d<bool> load(unsigned rows, unsigned columns, const unsigned *data)
  {
    Arr_2d<bool> res(rows, columns);
    for (unsigned i = 0; i < rows; ++i) 
      for (unsigned j = 0; j < columns; ++j)
        res[i][j] = !!*(data++);
    return(res);
  }
#include <iostream>
int main()
  {
    {
      Arr_2d<bool> tab = load(3, 3, ex1);
      std::cout << solve(tab) << 'n';
    }
    {
      Arr_2d<bool> tab = load(6, 8, ex2);
      std::cout << solve(tab) << 'n';
    }
    return(0);
  }

问题如下:

  y
 yxy    If you flip x, then you have to flip all the ys
  y

但是如果你这样想的话就很简单了:

  x
 yyy    If you flip x, then you have to flip all the ys
  y

是一样的,但现在解决方案很明显——你必须翻转第0行所有的1,这将翻转第1行和第2行的一些位,然后你必须翻转第1行所有的1,以此类推,直到你到达最后。

如果这确实是一款《熄灯》游戏,那么就会有大量资源详细说明如何解决这款游戏。这也很有可能是熄灯游戏算法的复制品,正如其他海报所提到的那样。

让我们看看我们是否不能解决第一个提供的样本难题,但是,至少提出一个算法的具体描述。

最初的难题似乎是可以解决的:

1 0 1
1 1 1
0 1 0

的诀窍是你可以通过改变下面一行的值来清除上面一行的1。我将按行和列提供坐标,使用基于1的偏移量,这意味着左上角的值是(1,1),右下角的值是(3,3)。

更改(2,1),然后更改(2,3),然后更改(3,2)。我将显示电路板的中间状态,并在下一步更改单元格的*。

1 0 1  (2,1)  0 0 1  (2,3)  0 0 0 (3, 2)  0 0 0
* 1 1 ------> 0 0 * ------> 0 1 0 ------> 0 0 0
0 1 0         1 1 0         1 * 1         0 0 0

这个棋盘是可以解的,步数是3。

伪算法如下:

flipCount = 0
for each row _below_ the top row:
  for each element in the current row:
    if the element in the row above is 1, toggle the element in this row:
      increment flipCount
    if the board is clear, output flipCount
if the board isnt clear, output "Impossible"

我希望这有助于;如果需要,我可以进一步详细说明,但这是标准熄灯解决方案的核心。顺便说一下,它与高斯消去有关;线性代数在一些奇怪的情况下会出现:)

最后,就代码的问题而言,似乎是以下循环:

for(int i=0; i<n-1; i++)
{
    for(int j=0; j<m-1; j++)
    {
        if(tab[i][j] == 1 && i > 0 && j > 0)
        {
            tab[i-1][j] = !tab[i-1][j];
            tab[i+1][j] = !tab[i+1][j];
            tab[i][j+1] = !tab[i][j+1];
            tab[i][j-1] = !tab[i][j-1];
            tab[i][j] = !tab[i][j];
            counter ++;
        }
    }
}

我想到了几个问题,但还是第一个假设:

  • i表示第i行,共有n行
  • j为第j列,共m列
  • 我现在指的是从0开始的索引,而不是1

如果是这种情况,则观察到以下内容:

  1. 您可以从1而不是0开始运行for i循环,这意味着您不再需要检查if语句
  2. 中i是否> 0。你应该去掉if语句中的for j> 0;这个检查意味着你不能翻转第一列中的任何东西
  3. 您需要更改for i循环中的n-1,因为您需要为最后一行运行此
  4. 您需要更改for j循环中的m-1,因为您需要为最后一列运行该循环(也参见第2点)
  5. 您需要检查当前行上方行的单元格,因此您应该检查选项卡[i-1][j] == 1
  6. 现在您需要为j-1, j+1和i+1添加边界测试,以避免读取超出矩阵的有效范围

把这些放在一起,你有:

for(int i=1; i<n; i++)
{
    for(int j=0; j<m; j++)
    {
        if(tab[i-1][j] == 1)
        {
            tab[i-1][j] = !tab[i-1][j];
            if (i+1 < n)
              tab[i+1][j] = !tab[i+1][j];
            if (j+1 < m)
              tab[i][j+1] = !tab[i][j+1];
            if (j > 0)
              tab[i][j-1] = !tab[i][j-1];
            tab[i][j] = !tab[i][j];
            counter ++;
        }
    }
}

一个小类,可以作为输入文件或测试6,5矩阵中第一行只有0的所有可能组合:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstdlib>
#include <ctime>

typedef std::vector< std::vector<int> > Matrix;
class MatrixCleaner
{
    public:
        void swapElement(int row, int col)
        {
            if (row >= 0 && row < (int)matrix.size() && col >= 0 && col < (int)matrix[row].size())
                matrix[row][col] = !matrix[row][col];
        }
        void swapElements(int row, int col)
        {
            swapElement(row - 1, col);
            swapElement(row, col - 1);
            swapElement(row, col);
            swapElement(row, col + 1);
            swapElement(row + 1, col);
        }
        void printMatrix()
        {
            for (auto &v : matrix)
            {
                for (auto &val : v)
                {
                    std::cout << val << " ";
                }
                std::cout << "n";
            }
        }
        void loadMatrix(std::string path)
        {
            std::ifstream fileStream;
            fileStream.open(path);
            matrix.resize(1);
            bool enconteredNumber = false;
            bool skipLine = false;
            bool skipBlock = false;
            for (char c; fileStream.get(c);)
            {
                if (skipLine)
                {
                    if (c != '*')
                        skipBlock = true;
                    if (c != 'n')
                        continue;
                    else
                        skipLine = false;
                }
                if (skipBlock)
                {
                    if (c == '*')
                        skipBlock = false;
                    continue;
                }
                switch (c)
                {
                case '0':
                    matrix.back().push_back(0);
                    enconteredNumber = true;
                    break;
                case '1':
                    matrix.back().push_back(1);
                    enconteredNumber = true;
                    break;
                case 'n':
                    if (enconteredNumber)
                    {
                        matrix.resize(matrix.size() + 1);
                        enconteredNumber = false;
                    }
                    break;
                case '#':
                    if(!skipBlock)
                        skipLine = true;
                    break;
                case '*':
                    skipBlock = true;
                    break;
                default:
                    break;
                }
            }
            while (matrix.size() > 0 && matrix.back().empty())
                matrix.pop_back();
            fileStream.close();
        }
        void loadRandomValidMatrix(int seed = -1)
        {
            //Default matrix
            matrix = {
                { 0,0,0,0,0 },
                { 0,0,0,0,0 },
                { 0,0,0,0,0 },
                { 0,0,0,0,0 },
                { 0,0,0,0,0 },
                { 0,0,0,0,0 },
            };
            int setNum = seed;
            if(seed < 0)
                if(seed < -1)
                    setNum = std::rand() % -seed;
                else
                    setNum = std::rand() % 33554432;
            for (size_t r = 1; r < matrix.size(); r++)
                for (size_t c = 0; c < matrix[r].size(); c++)
                {
                    if (setNum & 1)
                        swapElements(r, c);
                    setNum >>= 1;
                }
        }
        bool test()
        {
            bool retVal = true;
            for (int i = 0; i < 33554432; i++)
            {
                loadRandomValidMatrix(i);
                if( (i % 1000000) == 0 )
                    std::cout << "i= "  << i << "n";
                if (clean() < 0)
                {
//                  std::cout << "x";
                    std::cout << "n" << i << "n";
                    retVal = false;
                    break;
                }
                else
                {
//                  std::cout << ".";
                }
            }
            return retVal;
        }
        int clean()
        {
            int numOfSwaps = 0;
            try
            {
                for (size_t r = 1; r < matrix.size(); r++)
                {
                    for (size_t c = 0; c < matrix[r].size(); c++)
                    {
                        if (matrix.at(r - 1).at(c))
                        {
                            swapElements(r, c);
                            numOfSwaps++;
                        }
                    }
                }
            }
            catch (...)
            {
                return -2;
            }
            if (!matrix.empty())
                for (auto &val : matrix.back())
                {
                    if (val == 1)
                    {
                        numOfSwaps = -1;
                        break;
                    }
                }
            return numOfSwaps;
        }
        Matrix matrix;
};
int main(int argc, char **argv)
{
    std::srand(std::time(NULL));
    MatrixCleaner matrixSwaper;
    if (argc > 1)
    {
        matrixSwaper.loadMatrix(argv[argc - 1]);
        std::cout << "intput:n";
        matrixSwaper.printMatrix();
        int numOfSwaps = matrixSwaper.clean();
        std::cout << "noutput:n";
        matrixSwaper.printMatrix();
        if (numOfSwaps > 0)
            std::cout << "nresult = " << numOfSwaps << " matrix is clean now " << std::endl;
        else if (numOfSwaps == 0)
            std::cout << "nresult = " << numOfSwaps << " nothing to clean " << std::endl;
        else
            std::cout << "nresult = " << numOfSwaps << " matrix cannot be clean " << std::endl;
    }
    else
    {
        std::cout << "Testing ";
        if (matrixSwaper.test())
            std::cout << " PASSn";
        else
            std::cout << " FAILn";
    }

    std::cin.ignore();
    return 0;
}