POJ 3279 Fliptile 反转 + 点灯游戏

一、题目大意

本题目其实是关于一个点灯游戏的玩法,相信很多人多玩过这个游戏,每次电亮一盏灯,这盏灯周围的4个灯也都会一起点亮,本题目是要求出最少的操作次数,电亮所有的灯,并把电亮的灯输出在答案里。

二、解题思路

我们能够直到,同样一盏灯电亮两次,等于没点!所以我们记录一个数组f[i][j],用来放置每个坐标是否被电亮过。

然后我们只需要计算f[i][j]+f[i-1][j]+f[i+1][j]+f[i][j-1]+f[i][j+1]的和sum,如果sum是奇数,那么该位置与最开始输入的位置的颜色相反,否则颜色相同。

那么这就变成了一个反转问题,我们利用二进制,从0000,到1111(N=4时)枚举第一行的所有点灯情况,然后从第2行开始到M行循环所有坐标,判断如果它上一行灯是灭着的,就电亮这盏灯,否则就continue

然后到最后一行判断下,如果最后一行有灯是灭着的,那么这个情况无解(因为最后一行再改动一定会影响到上面的,那么其实不可能全亮了),遍历第一行的下一种情况,如果最后一行所有灯全亮,那么把当前f[i][j]数组求和,如果小于记录的ans,那么就保存下当前的结果。

最后判断下,如果ans从未被修改,那么输出IMPOSSIBLE,否则输出记录的结果

复杂性是 (2^N)*(MN),是可行的。

三、代码

#include <iostream>
using namespace std;
int towPow[27], revCnt[17][17], M, N, ansRevCnt, count, ans[17][17];
bool white[17][17];
void initTwoPow()
{
    towPow[0] = 1;
    for (int i = 1; i <= 20; i++)
    {
        towPow[i] = towPow[i - 1] * 2;
    }
}
void input()
{
    scanf("%d%d", &M, &N);
    int x;
    for (int i = 1; i <= M; i++)
    {
        for (int j = 1; j <= N; j++)
        {
            scanf("%d", &x);
            if (x == 0)
            {
                white[i][j] = true;
            }
            else
            {
                white[i][j] = false;
            }
        }
    }
    ansRevCnt = 0x3f3f3f3f;
}
void flushRevCnt()
{
    for (int i = 1; i <= M; i++)
    {
        for (int j = 1; j <= N; j++)
        {
            revCnt[i][j] = 0;
        }
    }
}
// 计算i,j坐标的一圈五个位置的结果
int calcRevCntAroundIJ(int i, int j)
{
    int result = revCnt[i][j];
    if (i > 1)
    {
        result += revCnt[i - 1][j];
    }
    if (j > 1)
    {
        result += revCnt[i][j - 1];
    }
    if (j < N)
    {
        result += revCnt[i][j + 1];
    }
    if (i < M)
    {
        result += revCnt[i + 1][j];
    }
    return result;
}
// 判断最后一行是否全是白色
bool judgeLastLine()
{
    for (int col = 1; col <= N; col++)
    {
        int currentRevCnt = calcRevCntAroundIJ(M, col);
        bool isWhite = white[M][col];
        if (currentRevCnt % 2 != 0)
        {
            isWhite = !white[M][col];
        }
        if (!isWhite)
        {
            return false;
        }
    }
    return true;
}
void saveAns()
{
    int currentRevCnt = 0;
    for (int i = 1; i <= M; i++)
    {
        for (int j = 1; j <= N; j++)
        {
            currentRevCnt += revCnt[i][j];
        }
    }
    if (currentRevCnt >= ansRevCnt)
    {
        return;
    }
    ansRevCnt = currentRevCnt;
    for (int i = 1; i <= M; i++)
    {
        for (int j = 1; j <= N; j++)
        {
            ans[i][j] = revCnt[i][j];
        }
    }
}
void solve()
{
    for (int i = 2; i <= M; i++)
    {
        for (int j = 1; j <= N; j++)
        {
            int beforeLineRevCnt = calcRevCntAroundIJ(i - 1, j);
            bool beforeLineIsWhite = white[i - 1][j];
            if (beforeLineRevCnt % 2 != 0)
            {
                beforeLineIsWhite = !white[i - 1][j];
            }
            if (!beforeLineIsWhite)
            {
                revCnt[i][j] = 1;
            }
        }
    }
    if (judgeLastLine())
    {
        saveAns();
    }
}
// 利用二进制,来枚举第一行翻与不翻的所有可能
void handleFirstLineByBin(int numBin)
{
    for (int j = N - 1; j >= 0; j--)
    {
        int x = numBin & towPow[j];
        x = x >> j;
        if (x == 1)
        {
            revCnt[1][N - j] = 1;
        }
        else
        {
            revCnt[1][N - j] = 0;
        }
    }
}
void outputAns()
{
    if (ansRevCnt == 0x3f3f3f3f)
    {
        printf("IMPOSSIBLE\n");
        return;
    }
    for (int i = 1; i <= M; i++)
    {
        for (int j = 1; j <= N; j++)
        {
            if (j != N)
            {
                printf("%d ", ans[i][j]);
            }
            else
            {
                printf("%d\n", ans[i][j]);
            }
        }
    }
}
int main()
{
    initTwoPow();
    input();
    int base = 1048576, len = towPow[N];
    for (int i = 0; i < len; i++)
    {
        flushRevCnt();
        int numBin = base + i;
        handleFirstLineByBin(numBin);
        solve();
    }
    outputAns();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值