POJ 2965 The Pilots Brothers' refrigerator(枚举+DFS)

原题地址

http://poj.org/problem?id=2965

题意:给定4*4矩阵,每个方格为’+’或’-‘,对每个方格状态的翻转都会引起所在行、所在列其它方格的翻转,求最小的翻转次数和翻转序列,使得所有方格都为’-‘。

解题思路

本题与前一篇博文《POJ 1753 Flip Game》的题目非常像,都是枚举相关的题目。

在POJ 1753里用到了状态压缩的方法,状态压缩的核心思想就是用二进制位来表示对应的位置的两种状态,整个4*4数组就可以用一个二进制数表示。这道题同样可以采用状态压缩的办法,但是编写代码时不太方便,也不太好理解,暂时用一般的数组方法来表示。

由于本题要求输出最优解的完整序列,因此采用带记录路径的DFS+回溯:

  1. 判断当前数组的情况是否满足开锁条件(即全为’-‘),如果满足,再这趟成功搜索所需的翻转次数Step,是否比之前解锁所需的最少次数minStep更小。同时将这趟搜索的临时路径保存到最终路径中。
  2. 在对每个位置处理时,要么不翻转该位置继续向下搜索,不改变翻转次数;要么翻转该位置后再向下搜索,记录翻转的位置,同时翻转次数加一。无非是这两种情况,用二叉树的观点来看,左子树代表不翻转,右子树代表翻转。
  3. 回溯思想的体现:某个位置处理完之后回溯到上一个位置,对上一个位置其他情况继续搜索。
  4. 结构体PATH的数组用于记录每趟DFS的路径,tmp存放翻转经过的临时路径, ans存放最终的路径。

AC代码

#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;

typedef struct node
{
    int x, y;
}PATH;

int handle[4][4]; //把手的开关情况
PATH tmp[20], ans[20];//tmp存放翻转经过的临时路径, ans存放最终的路径
int minStep = 32; //开锁需要的最小次数

void Init()
{
    memset(handle, 0, sizeof(handle));
    char c;
    for (int i = 0; i<4; ++i)
        for (int j = 0; j<4; ++j)
        {
            cin >> c;
            if(c == '+') //某位置有锁,标记为1
                handle[i][j] = 1;
        }
}

bool IsOpened() //判断是否开了锁
{
    for (int i = 0; i<4; ++i)
        for (int j = 0; j<4; ++j)
        {
            if (handle[i][j] == 1) //还存在锁
                return false;
        }
    return true;
}

void flip(int num) //翻转第num(0~15)个位置的锁
{
    int row = num/4, col = num%4;
    handle[row][col] = !handle[row][col]; //该锁自身取反
    for (int i = 0; i<4; ++i)
    {
        handle[row][i] = !handle[row][i]; //所在行取反
        handle[i][col] = !handle[i][col]; //所在列取反
    }
}

void DFS(int curr, int step) //curr当前要处理的节点,step处理到当前节点共翻转了几次
{
    if (IsOpened() == true) //已经能开锁
    {
        if (step < minStep) //更新最小次数
        {
            minStep = step;
            for (int i = 0; i<step; ++i) //更新当前最小次数的由来路径
                ans[i] = tmp[i]; //按字节复制
        }
        return;
    }

    if (curr > 15) return; //搜索失败,越界

    //对每个位置的可能情况做深搜,最后回溯(左子树为不翻转,右子树为翻转)

    //不翻转该位置,继续向下搜索
    DFS(curr+1, step);

    //翻转该位置,同时记录这次翻转的位置
    flip(curr);
    tmp[step].x = curr/4+1;
    tmp[step].y = curr%4+1;
    DFS(curr+1, step+1); //继续向下搜索
    flip(curr); //回溯

    return; //回到根节点
}

int main()
{
    Init();
    DFS(0, 0);
    cout << minStep << endl; //输出最小次数
    for (int i = 0; i<minStep; ++i) //输出最终路径
        cout << ans[i].x << ' ' << ans[i].y << endl;
    return 0;
}

内存占用: 712K 耗时:422ms(状态压缩会快一些)

posted @ 2017-04-05 14:12  Lecholin  阅读(152)  评论(0编辑  收藏  举报