[pieces] 关灯问题:分治、位运算、bool变量取反

这是一道自造题

    • 题目描述
    • 输入格式
    • 输出格式
    • 分析
    • 要点记录

题目描述

有一个有灯泡组成的矩阵,其中每行有6个,共5行

每一个灯都对应一个按钮,当按下按钮时,该按钮以及周围位置都会改变状态(由暗变亮或由亮变暗)。
如图:按下红色处灯泡对应的按钮,周围几个灯泡的状态都发生反转。
[pieces] 关灯问题:分治、位运算、bool变量取反_第1张图片
周围的灯泡状态不一定都是相同的,且关闭后,仍可以因为周围或者本身的操作而重新打开。如下:
[pieces] 关灯问题:分治、位运算、bool变量取反_第2张图片
[pieces] 关灯问题:分治、位运算、bool变量取反_第3张图片

输入格式

无输入。

输出格式

输出一个 5 × 6 5\times6 5×6的矩阵。按钮操作处为 1 1 1,否则为 0 0 0

分析

  • 第二次按下同一按钮,作用抵消;所以每个按钮只按一次
  • 各按钮按下的先后次序不同对最终的状态改变结果没有影响
  • 如果完全暴力枚举,枚举每个按钮的状态,将有 2 30 2^{30} 230种可能,数太大,应想法减少枚举数目:采用分治法
  • 利用分治:每次只考虑一行。对任意一行分析,上一行有确定未关闭数目的灯,我们只需要在这一行对应位置上按键,那么上一行就达成全灭。
    这种分治的策略在给定第一行状态运行时,一直计算到最后一行是 O ( 1 ) O(1) O(1)的。所以相当于我们把 O ( 2 m + n ) O(2^{m+n}) O(2m+n)降到了 O ( 2 n ) O(2^n) O(2n),这是很了不起的。
#include 
using namespace std;

bool orig[5][6], map[5][6], ans[5][6];

void init()//初始化状态的函数,也可以稍加改动成为一个任意初始状态的
{
    for (int i = 0; i < 5; i++)
        for (int j = 0; j < 6; j++)
            orig[i][j] = true;
}

void itop(bool *isPressed, int val)//将对应行的特征值转化为bool数组
{
    for (int i = 0; i < 6; i++)
        if (val & (1 << i))
            isPressed[i] = true;
        else
            isPressed[i] = false;
}

int ptoi(bool *pressed)//将对应行的bool数组转化为特征值
{
    int res = 0;
    for (int i = 0; i < 6; i++)
        if (pressed[i])
            res |= (1 << i);
    return res;
}

void Turn(int i, int j)//对矩阵进行操作。注意bool的取反
{
    if (j > 0)
        map[i][j - 1] ^= true;
    map[i][j] ^= true;
    if (j < 5)
        map[i][j + 1] ^= true;
    if (i < 4)
        map[i + 1][j] ^= true;
}

void solve()
{
    for (int i = 0; i < (1 << 6); i++)
    {
        memcpy(map, orig, sizeof(orig));//由于每次操作都假定了重新推导,所以得复盘
        int press = i;
        for (int j = 0; j < 5; j++)
        {
            itop(ans[j], press);
            for (int k = 0; k < 6; k++)
                if (ans[j][k])
                    Turn(j, k);
            press = ptoi(map[j]);//把上一行还亮着的点记录成特征值
        }
        if (press == 0)
        {
            for (int j = 0; j < 5; j++)
            {
                cout << ans[j][0];
                for (int k = 1; k < 6; k++)
                    cout << ' ' << ans[j][k];
                cout << endl;
            }
        }
    }
}

int main()
{
    init();
    solve();
}

要点记录

  • 从这个例子中我们可以很容易地感受到位运算的灵活性。
  • 这里由于每次都做了假设的推演,所以要保存初始状态。这个很关键
  • 尽量使用成对的变量i, j或者x, y表示横纵坐标。否则很容易习惯性出错。比如solve()中使用j, k造成了严重的错误。
  • bool变量并不是不能取反的,其计算和其他常见内置类型是一样的。

你可能感兴趣的:(C/C++,#,分治)