解决Flood-It-like拼图的最小点击次数

时间:2015-09-16 20:21:11

标签: c++ algorithm backtracking

我有网格N×M,其中每个单元格用一种颜色着色。

当玩家点击颜色为α的网格的任何单元格时,网格最左上角的颜色为β的单元格会接收颜色α,但不仅仅是:所有连接到的网格的单元格通过仅使用颜色α或β的路径的源也接收颜色α。

只应在水平和垂直方向上考虑单元之间的连接以形成路径。例如,当玩家点击左图中突出显示的单元格时,网格会在右侧接收图形的颜色。游戏的目标是使网格单色化。

Click Result

输入说明

  

输入的第一行由2个整数N和M(1≤N≤4,1≤M≤5)组成,它们分别代表网格的行数和列数。下面的N行描述了网格的初始配置,用0到9之间的整数表示每种颜色。输入不包含任何其他行。

输出说明

  

打印一行,其中包含一个整数,表示玩家为使网格单色而必须执行的最小点击次数。

输入样本

  

1:

     
    

4 5
    01234
    34567个
    67890个
    90123

  
     

2:

     
    

4 5
    01234
    12345个
    23456个
    34567

  
     

3:

     
    

4 5
    00162
    30295个
    45033个
    01837

  

输出样本

  

1:

     
    

12

  
     

2:

     
    

7

  
     

3:

     
    

10

  

我正在尝试找回带回溯的解决方案(因为时间限制为8秒,并且网格尺寸很小)。但它超出了时间限制。有些人在0秒时就完成了它。

还有其他算法可以解决这个问题吗?

#include <stdio.h>
#include <string.h>

#define MAX 5
#define INF 999999999

typedef int signed_integer;

signed_integer n,m,mink;
bool vst[MAX][MAX];

signed_integer flood_path[4][2] = {
    {-1,0},
    {1,0},
    {0,1},
    {0,-1}
};

//flood and paint all possible cells... the root is (i,j)
signed_integer flood_and_paint(signed_integer cur_grid[MAX][MAX],signed_integer i, signed_integer j, signed_integer beta, signed_integer alpha, signed_integer colors[]){
    //invalid cell
    if (vst[i][j] || i < 0 || i >= n || j < 0 || j >= m)
        return 0;

    //mark existent colors
    colors[cur_grid[i][j]] = 1;

    //only alpha and beta colors counts
    if (cur_grid[i][j] != beta && cur_grid[i][j] != alpha)
        return 0;

    //mark (i,j) as visited and change its color
    vst[i][j] = true;
    cur_grid[i][j] = alpha;

    //floodit !
    signed_integer ret = 1;
    for (signed_integer k = 0; k < 4; k++)
        ret += flood_and_paint(cur_grid,i + flood_path[k][0], j + flood_path[k][1], beta, alpha, colors);

    //how many cells change
    return ret;
}

void backtrack(signed_integer cur_grid[MAX][MAX],signed_integer k,signed_integer _cont, signed_integer alpha) {
    //bigger number of clicks for this solution ? ... getting back
    if(k >= mink)
        return;

    signed_integer colors[10];
    memset(vst, false, sizeof(vst));
    memset(colors, 0, sizeof(colors));

    signed_integer beta = cur_grid[0][0];
    signed_integer cont = flood_and_paint(cur_grid, 0, 0, beta, alpha, colors);

    //there are alpha colors to change and no beta colors to change
    colors[alpha] = 1;
    colors[beta]  = 0;

    //all squares on same color
    if (cont == n * m) {
        mink = k;
        return;
    }

    //this solution is equals to another ? ... getting back
    if (cont == _cont)
        return;

    ++k;//new click

    //copy this matrix and backtrack
    signed_integer copy[MAX][MAX];
    for (signed_integer c = 0; c < 10; ++c){
        if (colors[c] && c != cur_grid[0][0]) {
            memcpy(copy, cur_grid,n*m*sizeof(signed_integer));
            backtrack(copy,k,cont,c);
        }
    }
}

void cleanBuffer(){
     while (getchar() != '\n');
}

int main(void) {
    signed_integer grid[MAX][MAX];
    scanf("%d %d",&n,&m);
    for (signed_integer i = 0; i < n; ++i) {
        cleanBuffer();
        for (signed_integer j = 0; j < m; ++j){
            grid[i][j] = getchar() - '0';
        }
    }
    mink = INF;
    backtrack(grid,0, 0, grid[0][0]);
    printf("%d\n",mink);
    return 0;
}

2 个答案:

答案 0 :(得分:5)

高水平改善

请注意,单元格是原始颜色或最后选择的颜色。

这意味着我们可以通过20位表示电路板的当前状态(标记每个4 * 5单元是否包含原始颜色),以及0到9范围内的数字给出最后一个颜色选择。

这导致最多可探索1000万个州。回溯函数可以避免在它到达已经访问过的状态时必须递归。我希望这种改变可以使你的解决方案更快。

低水平改善

通过20位掩码表示状态,最后一种颜色也使状态更新和恢复更快,因为只需要更改2个数字而不是整板的memcpy。

答案 1 :(得分:3)

如果您认为4x5电路板为&#34; 19个方格,您可以点击&#34;,这表示有19个!或121,645,100,408,832,000组合检查。但是,有一些优化措施可以将组合数量大幅减少到只有几十个:

关于游戏策略的观察

  • 点击具有相同颜色的不同方块具有相同的效果。因此,应该将电路板视为&#34; 9种颜色(或更少),您可以点击&#34;。
  • 相同颜色的相邻方块应视为组;他们总是一起行动。在下面的示例中,右下方的三个白色方块组成了这样一个组。
  • 只有点击与角落组相邻的颜色才有意义。在下面的示例的第一步中,只有单击粉红色,绿色,橙色或白色方块才有意义。
  • 当几个独特颜色的组(其中只有一个组具有特定颜色)与角组相邻时,单击它们的顺序并不重要。在下面的示例中,单击5和3之后,单击4,7,8和9的任何顺序都将具有相同的结果。
  • 当某个颜色的所有组都与角组相邻时,它们可被视为唯一颜色的组;每种颜色只需一次点击就可以连接它们。在下面的示例中,点击5和3后,两个粉红色方块和两个绿色方块成为两个独特颜色的组
  • 当电路板只有唯一颜色的组时,必要的点击次数等于未连接的组的数量。在下面的示例中,单击5和3后,只需要再点击8次。
  • 如果只有一个未连接的组与角组具有相同的颜色,并且它们被多个其他组分隔,则可以将该组视为唯一颜色的组。
  • 减少点击次数意味着一次连接多个群组。当几个相同颜色的组与角组相邻时(例如,在下面的示例的步骤3中单击1或2时),或者通过单击将角组与具有相同颜色的组分开的组(如示例中的步骤1和2所示。

monochrome game - example 3 start

基于最优策略的算法

使用上面列出的规则的基于最优策略的递归算法,针对每次递归执行以下步骤:

  
      
  1. 如果只剩下唯一颜色的组,则点击次数等于未连接组的数量;返回此号码。
  2.   
  3. 如果与角落组颜色相同的组仅与另一个组分开,请单击此其他组并递归。如果存在多个选项,请尝试所有选项。
  4.   
  5. 如果一个或多个独特颜色的组(或某些颜色的所有组)与角组相邻,请以任何顺序单击它们。然后从步骤1重新评估电路板。
  6.   
  7. 尝试点击角落组旁边的每种颜色并递归。
  8.   

蛮力算法的Javascript实现需要数千万次递归,例如问题中的示例1和3,执行时间远远超过8秒限制。在实现上述优化之后,示例1通过仅38次递归解决,并且执行时间为几毫秒。例2和3甚至更快。