有一个矩形的硬币网格,其中头部由值1表示,尾部由值0表示。您使用2D整数数组表(1到10行/列,包括1和10行)来表示。 / p>
在每次移动中,您选择网格中的任何单个单元格(R,C)(第R行,第C列)并翻转所有单元格中的硬币(r,c),其中r介于0和0之间R,包括,并且c在0和C之间,包括0和C.翻转硬币意味着将单元格的值从零反转为一或一到零。
返回将网格中的所有单元格更改为尾部所需的最小移动次数。这总是可能的。
示例:
1111
1111
returns: 1
01
01
returns: 2
010101011010000101010101
returns: 20
000
000
001
011
returns: 6
这是我试过的: 由于翻转的顺序无关紧要,并且两次移动硬币就像完全没有移动一样,我们可以找到翻转硬币的所有不同组合,并最小化良好组合的大小(这意味着那些给所有尾巴。)
这可以通过制作一个由所有硬币组成的集合来完成,每个硬币由一个索引代表。(即,如果总共有20个硬币,则该集合将包含20个元素,给它们索引1到20)。然后制作所有可能的子集,看看它们中的哪一个给出了答案(即,如果对子集中的硬币进行移动则给我们所有的尾巴)。最后,最小化良好组合的大小。
我不知道我是否能够过于清楚地表达自己...如果你愿意,我会发一个代码。 无论如何,这种方法太耗费时间和浪费,并且对于没有硬币> 20(在我的代码中)不可能。 如何解决这个问题?
答案 0 :(得分:9)
我认为贪婪算法就足够了,每枚硬币只需一步。
每一个动作都会翻转棋盘的一个矩形子集。有些硬币包含在更多子集中:左上角(0,0)的硬币位于每个子集中,右下角的硬币只有一个子集,即包含每个硬币的硬币。
因此,选择第一步是显而易见的:如果必须翻转右下角,请翻转每一枚硬币。消除可能的行动。
现在,右下方硬币的左边和右边的邻居只能通过一次剩余移动来翻转。因此,如果必须执行此移动,请执行此操作。邻居的评估顺序无关紧要,因为它们并不是彼此的真正替代品。但是,光栅图案应该足够了。
重复直到完成。
这是一个C ++程序:
#include <iostream>
#include <valarray>
#include <cstdlib>
#include <ctime>
using namespace std;
void print_board( valarray<bool> const &board, size_t cols ) {
for ( size_t i = 0; i < board.size(); ++ i ) {
cout << board[i] << " ";
if ( i % cols == cols-1 ) cout << endl;
}
cout << endl;
}
int main() {
srand( time(NULL) );
int const rows = 5, cols = 5;
valarray<bool> board( false, rows * cols );
for ( size_t i = 0; i < board.size(); ++ i ) board[i] = rand() % 2;
print_board( board, cols );
int taken_moves = 0;
for ( size_t i = board.size(); i > 0; ) {
if ( ! board[ -- i ] ) continue;
size_t sizes[] = { i%cols +1, i/cols +1 }, strides[] = { 1, cols };
gslice cur_move( 0, valarray<size_t>( sizes, 2 ),
valarray<size_t>( strides, 2 ) );
board[ cur_move ] ^= valarray<bool>( true, sizes[0] * sizes[1] );
cout << sizes[1] << ", " << sizes[0] << endl;
print_board( board, cols );
++ taken_moves;
}
cout << taken_moves << endl;
}
答案 1 :(得分:3)
不是c ++。同意@Potatoswatter认为最佳解决方案是贪婪的,但我想知道线性丢番图系统是否也有效。这个Mathematica函数可以做到:
f[ei_] := (
xdim = Dimensions[ei][[1]];
ydim = Dimensions[ei][[2]];
(* Construct XOR matrixes. These are the base elements representing the
possible moves *)
For[i = 1, i < xdim + 1, i++,
For[j = 1, j < ydim + 1, j++,
b[i, j] = Table[If[k <= i && l <= j, -1, 0], {k, 1, xdim}, {l, 1, ydim}]
]
];
(*Construct Expected result matrix*)
Table[rv[i, j] = -1, {i, 1, xdim}, {j, 1, ydim}];
(*Construct Initial State matrix*)
Table[eiv[i, j] = ei[[i, j]], {i, 1, xdim}, {j, 1, ydim}];
(*Now Solve*)
repl = FindInstance[
Flatten[Table[(Sum[a[i, j] b[i, j], {i, 1, xdim}, {j, 1, ydim}][[i]][[j]])
eiv[i, j] == rv[i, j], {i, 1, xdim}, {j, 1, ydim}]],
Flatten[Table[a[i, j], {i, 1, xdim}, {j, 1, ydim}]]][[1]];
Table[c[i, j] = a[i, j] /. repl, {i, 1, xdim}, {j, 1, ydim}];
Print["Result ",xdim ydim-Count[Table[c[i, j], {i, 1, xdim}, {j, 1,ydim}], 0, ydim xdim]];)
使用示例调用时(-1而不是0)
ei = ({
{1, 1, 1, 1},
{1, 1, 1, 1}
});
f[ei];
ei = ({
{-1, 1},
{-1, 1}
});
f[ei];
ei = {{-1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1,
1, -1, 1, -1, 1, -1, 1}};
f[ei];
ei = ({
{-1, -1, -1},
{-1, -1, -1},
{-1, -1, 1},
{-1, 1, 1}
});
f[ei];
结果是
Result :1
Result :2
Result :20
Result :6
或者:)
在我的可怜人的笔记本电脑上,在90秒内解决20x20随机问题。
答案 2 :(得分:2)
基本上,你在右边界和右边界拿着N + M-1硬币并解决它们,然后只是在其他一切上递归调用算法。这基本上是Potatoswatter所说的。下面是一个非常简单的递归算法。
Solver(Grid[N][M])
if Grid[N-1][M-1] == Heads
Flip(Grid,N-1,M-1)
for each element i from N-2 to 0 inclusive //This is empty if N is 1
If Grid[i][M-1] == Heads
Flip(Grid,i,M-1)
for each element i from M-2 to 0 inclusive //This is empty if M is 1
If Grid[N-1][i] == Heads
Flip(Grid,N-1,i)
if N>1 and M > 1:
Solver(Grid.ShallowCopy(N-1, M-1))
return;
注意:通过让Solver拥有Grid的宽度和高度的参数来实现Grid.ShallowCopy可能是有意义的。我只调用它Grid.ShallowCopy
来表示你不应该传入一个网格副本,尽管C ++默认情况下不会默认使用数组。
答案 3 :(得分:2)
矩形(x,y)被翻转的简单标准似乎是:当左上方(x,y)的2x2平方中的1的数量是奇数时。
(Python中的代码)
def flipgame(grid):
w, h = len(grid[0]), len(grid)
sol = [[0]*w for y in range(h)]
for y in range(h-1):
for x in range(w-1):
sol[y][x] = grid[y][x] ^ grid[y][x+1] ^ grid[y+1][x] ^ grid[y+1][x+1]
for y in range(h-1):
sol[y][w-1] = grid[y][w-1] ^ grid[y+1][w-1]
for x in range(w-1):
sol[h-1][x] = grid[h-1][x] ^ grid[h-1][x+1]
sol[h-1][w-1] = grid[h-1][w-1]
return sol
如果应该翻转矩形(x,y),返回的2D数组的位置(x,y)为1,因此其中1的数量就是原始问题的答案。
编辑:了解其工作原理: 如果我们做移动(x,y),(x,y-1),(x-1,y),(x-1,y-1),则仅反转square(x,y)。这导致了上面的代码。解决方案必须是最优的,因为有2 ^(h w)可能的电路板配置和2 ^(h w)可能的方式来转换电路板(假设每次移动都可以完成0或1次)。换句话说,只有一种解决方案,因此上面产生了最优解。
答案 4 :(得分:-3)
您可以使用递归试验。
您至少需要移动计数并传递矢量的副本。您还需要设置最大移动截止值,以设置搜索树的每个节点出来的分支宽度的限制。请注意,这是一种“暴力”方法。“
您的一般算法结构将是:
const int MAX_FLIPS=10;
const unsigned int TREE_BREADTH=10;
int run_recursion(std::vector<std::vector<bool>> my_grid, int current flips)
{
bool found = true;
int temp_val = -1;
int result = -1;
//Search for solution with for loops; if true is found in grid, found=false;
...
if ( ! found && flips < MAX_FLIPS )
{
//flip coin.
for ( unsigned int more_flips=0; more_flips < TREE_BREADTH; more_flips++ )
{
//flip one coin
...
//run recursion
temp_val=run_recursion(my_grid,flips+1)
if ( (result == -1 && temp_val != -1) ||
(temp_val != -1 && temp_val < result) )
result = temp_val;
}
}
return result;
}
...提前抱歉任何拼写错误/次要语法错误。想为您制作快速解决方案的原型,而不是编写完整的代码......
或者更容易,你可以使用蛮力的线性试验。使用外部for循环将是试验次数,内部for循环将在试验中翻转。在每个循环中,您需要翻转并检查是否成功,从上方回收成功并翻转代码。成功会使内循环短路。在内部循环的末尾,将结果存储在数组中。如果在max_moves之后失败,则存储-1。搜索最大值。
更优雅的解决方案是使用多线程库来启动一堆线程翻转,并在找到匹配时向其他人发送一个线程信号,并且如果匹配低于到目前为止运行的步骤数另一个线程,该线程以失败退出。
我建议MPI,但CUDA可能会赢得你的褐色点,因为它现在很热。
希望有所帮助,祝你好运!