Flip Games 题解报告:广度优先搜索

Flip Games 题解报告

问题描述:

具体问题描述就不在此展开,有兴趣的朋友可以参看下面的链接:http://cxsjsxmooc.openjudge.cn/test2/G/ 。简单来说就是给定一个棋盘的状态量(黑白相间),每次点击一个棋子,会把其及周围的棋子改变颜色,问最少需要点击几次棋子才能达到纯黑或者纯白的状态。

在这里插入图片描述

问题分析

由于题目中问的是最优解,我们很容易想到使用广度优先搜索的方法来获得答案,关于BFS算法的初步介绍请参考我的上一篇博客。 https://blog.csdn.net/qq_27848347/article/details/102874472

状态表达

BFS算法背后的思想并不复杂,就是用队列来模拟分层搜索的过程,但在搜索时,我们需要对当前的状态加一标记,以避免重复搜索。那么如何在保证有较快的搜素速度的前提下,以较小的空间存储状态(节点)数目巨大的状态?显然简单粗暴的使用一个字符串实在是浪费空间。作者在这里使用了一个16位的2进制数来表达当前棋盘的状态量,一是比较直观,二则方便后面的翻转操作。

以上图为例,其状态量可直接表达为:

0 1 0 1
1 1 1 1
0 0 1 0
0 1 1 0

在计算机存储为:

010111100100110

显然一个int变量就足以满足我们需求,大大节省了相对于字符串的空间开支。

翻转操作

按照题目要求,每点击一个棋子,会将其及其周围的棋子的颜色发生改变,如果按照常规思路,需要对当前棋盘的棋子进行遍历,然后逐个进行翻转操作,时间复杂度较高。考虑到上文中,我们将每个棋子的状态用一个bit的0-1表达,使用位运算是一个很直接的考虑。

考虑到与或运算的性质:

  • 0 与或 任何数,任何数不发生变化:对应于棋盘棋子在效果范围外,不受影响
  • 1 与或 任何数,任何数取反:对应于棋子在效果范围内,状态取反

我们很自然的想到将当前棋盘状态与一个操作对应的数值进行与或操作,达到翻转的效果。

以点击棋盘右下角为例,其二进制表达可以写为:

0 0 0 0
0 0 0 0
0 0 0 1
0 0 1 1

将其与状态表达进行与或的位运算,可以很方便的改变棋盘的当前状态。

源代码

其他就没有什么注意事项,直接看代码就好。

#include <iostream>
#include <queue>
#include <cstdio>
using namespace std;

const int board_size = 4;
const int direction_num = 4;
struct Piece
{
    int status; //状态值
    int steps;  //步数
    Piece(int st, int step) : status(st), steps(step) {}
};
int direction[direction_num][2] = {
    {-1, 0}, {0, 1}, {1, 0}, {0, -1}};
int pos[board_size * board_size];            //16种点击反转可能性
bool marker[1 << (board_size * board_size)]; //2^16次方摆放状态

bool inBoard(int x, int y)
{
    if (x >= 0 && x < board_size && y >= 0 && y < board_size)
        return true;
    else
        return false;
}
void FlipInitialize()
{
    for (int i = 0; i < board_size; i++)
    {
        for (int j = 0; j < board_size; j++)
        {
            // i*size+j  即为编号
            int value = 1 << (i * board_size + j);
            for (int k = 0; k < direction_num; k++)
            {
                int next_x = i + direction[k][0];
                int next_y = j + direction[k][1];
                if (inBoard(next_x, next_y))
                    value += 1 << (next_x * board_size + next_y);
            }
            pos[i * board_size + j] = value;
        }
    }
}
int BFS(int value)
{
    queue<Piece> q;
    Piece s = Piece(value, 0);
    q.push(s);
    marker[s.status] = true;
    while (!q.empty())
    {
        Piece cur = q.front();
        q.pop();
        //盘面全黑或者全白(2^16 -1)时结束,并返回步数
        if (cur.status == 0 || cur.status == (1 << (board_size * board_size)) - 1) //注意加减运算符比位运算符等级高!!
        {
            return cur.steps;
        }
        //搜索16个位置
        for (int i = 0; i < board_size * board_size; ++i)
        {
            //通过异或运算得到翻转后的状态
            Piece next = Piece(cur.status ^ pos[i], cur.steps + 1);
            if (!marker[next.status])
            {
                q.push(next);
                marker[next.status] = true;
            }
        }
    }
    return -1; //无法到达目标状态 返回-1
}
int main()
{
    freopen("E:\\text.txt", "r", stdin);
    FlipInitialize();
    char str[5];
    int value = 0;
    for (int i = 0; i < board_size; i++)
    {
        cin >> str;
        for (int j = 0; j < board_size; j++)
        {
            if (str[j] == 'w')
                value += 1 << (i * board_size + j);
            // 加上 1<<(编号) 即可将此位置设置为1
        }
    }
    int ans = BFS(value);
    if (ans >= 0)
        cout << ans << endl;
    else
    {
        cout << "Impossible" << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值