画家问题-局部枚举

本文介绍了一种解决正方形墙涂色问题的算法。通过枚举第一行涂色的所有可能性来找出最少涂色次数,使得整个墙面变为黄色。文章详细解释了算法思路,并提供了完整的C++代码实现。

目录

描述

输入

输出

样例输入

样例输出

思路

代码示范


描述

有一个正方形的墙,由N*N个正方形的砖组成,其中一些砖是白色的,另外一些砖是黄色的。Bob是个画家,想把全部的砖都涂成黄色。但他的画笔不好使。当他用画笔涂画第(i, j)个位置的砖时, 位置(i-1, j)、 (i+1, j)、 (i, j-1)、 (i, j+1)上的砖都会改变颜色。请你帮助Bob计算出最少需要涂画多少块砖,才能使所有砖的颜色都变成黄色。

 

输入

本题有多组测试数据(25组以内),每一组中第一行是一个整数n (1≤ n ≤15),表示墙的大小。接下来的n行表示墙的初始状态。每一行包含n个字符。第i行的第j个字符表示位于位置(i,j)上的砖的颜色。“w”表示白砖,“y”表示黄砖。

输出

输出为一行,如果Bob能够将所有的砖都涂成黄色,则输出最少需要涂画的砖数,否则输出“inf”。

样例输入

5
wwwww
wwwww
wwwww
wwwww
wwwww

样例输出

15

思路

使用暴力算法,局部枚举第一行以确定所有上色可能。

核心:为使上一行颜色恒为黄色,下一行上色情况一定。以此类推,只要对第一行所有情况进行枚举,即可确定整面墙体上色情况。

1.封装函数judge():用于判断该第一行情况下是否能完成整墙上色。

  • 算法思路:先置第一行上色情况。
  • 根据上一行上色情况判断本行是否上色。依次计算每行上色。
  • 判断该上色思路能否达成整墙上色。

2.封装函数enumerate():

  • 枚举思路:每格砖只有上色与否两种可能,用二进制表示。

 3.考虑到连续测试,每次开始务必初始化上色统计。

代码示范

#define _CRT_SECURE_NO_WARNINGS    //所有头文件和宏定义为个人爱好
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <cstring>
#include <string.h>
#include <math.h>
#include <cmath>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
#define maxf 16
using namespace std;

int judge(int(*ori)[maxf], int(*paint)[maxf], int n)
{
    for (int i = 0; i < n-1; i++)
    {
        for (int j = 0; j < n; j++)
        {
            paint[i + 1][j] = ori[i][j] + paint[i][j];    //根据上一行情况统计本行上色情况
            if (i - 1 > -1)paint[i + 1][j] += paint[i - 1][j];
            if (j - 1 > -1)paint[i + 1][j] += paint[i][j - 1];
            if (j + 1 < n)paint[i + 1][j] += paint[i][j + 1];
            paint[i + 1][j] %= 2;
        }
    }
    for (int j = 0; j < n; j++)
    {
        int flag=paint[n - 2][j] + paint[n - 1][j];    //判断在上一行限制下,最后一行能否转为黄色
        if (j + 1 < n)flag += paint[n - 1][j+1];
        if (j - 1 > -1)flag += paint[n - 1][j - 1];
        flag %= 2;
        if (flag != ori[n - 1][j])return maxf * maxf;    //原色为白,不上色;原色为黄,被迫上色均为上色失败
    }
    int step = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)step += paint[i][j];    //统计上色次数
    }
    return step;
}
int enumerate(int (*ori)[maxf], int(*paint)[maxf], int n)
{
    int minc = maxf * maxf;
    int step = 0;
    while (!paint[0][n])    //二进制溢出,枚举完成
    {
        step = judge(ori, paint, n);
        if (step < minc)minc = step;
        paint[0][0]++;    //设置第一行情况
        int cnt = 0;    //进行二进制算法,从低位开始检查
        while (paint[0][cnt] > 1)    //逢二进一
        {
            paint[0][cnt] = 0;
            paint[0][++cnt]++;
        }
    }
    return minc;
}
int main()
{
    int n;
    while (cin >> n)
    {
        int ori[maxf][maxf];
        int paint[maxf][maxf];
        for (int i = 0; i < n; i++)
        {
            char c=getchar();    //输入墙面原始色彩
            for (int j = 0; j < n; j++)
            {
                c = getchar();
                if (c == 'w')ori[i][j] = 1;    //需要上色
                else ori[i][j] = 0;
                paint[i][j] = 0;    //初始化上色情况
            }
        }
        getchar();
        paint[0][n] = 0;    //初始化枚举情况
        int step = enumerate(ori, paint, n);
        if (step == maxf * maxf)cout << "inf" << endl;
        else cout << step << endl;
    }
    return 0;
}

### 问题分析 Coursera 课程中的“画家问题”是一个经典的编程题,其目标是通过最少的涂画次数将墙上的所有砖块变为黄色。每次涂画一个砖块时,该砖块及其上下左右四个相邻砖块的颜色会翻转(白色变黄色,黄色变白色)。这一问题可以通过**枚举**方法解决,尤其是通过**二进制枚举**来尝试所有可能的涂画组合。 由于每次涂画会影响多个砖块的状态,因此不能简单地逐个涂画,而是需要考虑所有可能的组合,计算出最少的操作次数。 ### 算法思路 1. **状态表示**: - 墙的初始状态用一个二维数组表示,其中 `'w'` 表示白色砖块,`'y'` 表示黄色砖块。 - 每次涂画操作会影响当前砖块及其上下左右的砖块。 2. **枚举策略**: - 由于砖块数量有限(最大为 $15 \times 15 = 225$),可以使用二进制枚举尝试所有可能的涂画方案。 - 每个涂画方案可以表示为一个二进制数,其中每一位代表是否涂画对应位置的砖块。 3. **翻转操作模拟**: - 对于每个涂画方案,模拟所有涂画操作后,检查是否所有砖块都变为黄色。 - 如果是有效的方案,则记录该方案的涂画次数,并更新最小值。 4. **优化**: - 由于枚举所有可能的涂画方案会导致时间复杂度较高,可以采用剪枝策略减少无效尝试。 - 可以利用位运算加速状态翻转的计算。 ### Python 实现 以下是一个完整的 Python 实现,用于解决 Coursera 课程中的“画家问题”: ```python def flip(board, x, y, n): """翻转指定砖块及其相邻砖块的颜色""" directions = [(-1, 0), (1, 0), (0, -1), (0, 1), (0, 0)] for dx, dy in directions: nx, ny = x + dx, y + dy if 0 <= nx < n and 0 <= ny < n: board[nx][ny] = 'y' if board[nx][ny] == 'w' else 'w' def count_operations(n, board): """枚举所有可能的涂画方案,计算最小操作次数""" min_ops = float('inf') # 枚举所有可能的涂画方案(2^(n*n) 种) for mask in range(1 << (n * n)): temp_board = [row[:] for row in board] ops = 0 # 根据当前方案进行涂画 for i in range(n): for j in range(n): if (mask >> (i * n + j)) & 1: flip(temp_board, i, j, n) ops += 1 # 检查是否所有砖块都变为黄色 all_yellow = all(temp_board[i][j] == 'y' for i in range(n) for j in range(n)) if all_yellow: min_ops = min(min_ops, ops) return min_ops def main(): t = int(input()) for _ in range(t): n = int(input()) board = [] for _ in range(n): row = list(input().strip()) board.append(row) result = count_operations(n, board) print(result) if __name__ == "__main__": main() ``` ### 代码说明 - **flip 函数**:模拟涂画操作,翻转指定砖块及其相邻砖块的颜色。 - **count_operations 函数**:枚举所有可能的涂画方案,并计算每种方案下的操作次数。 - **main 函数**:读取输入数据并调用核心函数,输出结果。 ### 性能分析 - **时间复杂度**:最坏情况下为 $O(2^{n^2} \cdot n^2)$,适用于 $n \leq 15$ 的输入限制。 - **空间复杂度**:$O(n^2)$,用于存储墙的状态。 ### 注意事项 - 由于枚举方案数量较多,建议在实际运行时对输入数据进行限制或优化枚举策略。 - 在实际编程练习中,可以结合位运算进一步优化性能。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值