问题描述
有一个正方形的墙,由N*N个正方形的砖组成,其中一些砖是白色的,另外一些砖是黄色的。Bob是个画家,想把全部的砖都涂成黄色。但他的画笔不好使。当他用画笔涂画第(i, j)个位置的砖时, 位置(i-1, j)、 (i+1, j)、 (i, j-1)、 (i, j+1)上的砖都会改变颜色。请你帮助Bob计算出最少需要涂画多少块砖,才能使所有砖的颜色都变成黄色。
这个题本来如果枚举整个棋盘的话,为
,
但我们可以发现一个关系,在前一行没有涂黄的格子对应下一行,如果可以涂色的话,那么上一行就圆满了!!
这就和我们的递推想法达成了一致。递推就是用先前确定的值来表示新的case,在这里也就是说,通过这样的规则,我们只需要枚举第一行,整个画板达成全黄的所有方案就已经被枚举过了。
如果枚举第一行的
种方案的某种方案不能使得最后一行全黄,说明这种方法行不通。
于是有如下:
示例代码
#include <iostream>
#include <cstring>
using namespace std;
bool orig[17][17], map[17][17], res[17][17];
int n;
void init()
{
cin >> n;
char tmp;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
{
cin >> tmp;
if (tmp == 'w')
orig[i][j] = 1;
}
}
int ptoi(bool *pressed)
{
int tmp = 0;
for (int i = 0; i < n; i++)
if (pressed[i])
tmp |= (1<<i);
return tmp;
}
int itop(int val, bool *isPressed)
{
for (int i = 0; i < n; i++)
if (val & (1 << i))
isPressed[i] = true;
else isPressed[i] = false;
}
void turn(int i, int j)//对第j行第k列的格子进行涂色操作
{
if (j > 0)//从第二行开始。i已经保证大于零了,所以不用判断
map[i][j-1] ^= true;//异或运算使对应格子颜色反转
map[i][j] ^= true;
if (j < n-1)
map[i][j+1] ^= true;
if (i < n-1)
map[i+1][j] ^= true;
}
void solve()
{
int ans = 15 * 15 + 10;
for (int i = 0; i < (1<<n); i++)//枚举第一行的所有可能方案
{
memcpy(map, orig, sizeof orig);
int p = i;
for (int j = 0; j < n; j++)//逐行完善枚举结果
{
itop(p, res[j]);
for (int k = 0; k < n; k++)//实施枚举结果,并传递信息给下一次
if (res[j][k])
turn(j, k);
p = ptoi(map[j]);
}
if (p == 0)//退出时,p代表最后一行的信息,尝试更新结果
{
int cnt = 0;
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
if (res[j][k]) cnt++;
if (cnt < ans)
ans = cnt;
}
}
if (ans < 235)
cout << ans;
else cout << "inf";
}
int main()
{
init();
solve();
}
优化代码:
- 减少了前后传递信息的过程中中利用数组做中介的愚蠢行为
- 同时使用inline函数(虽然函数比较长……且算空间换时间)加inline的优化效果并不明显(仅个位数ms)。
#include <iostream>
#include <cstring>
using namespace std;
bool orig[17][17], map[17][17], res[17][17];
int n;
void init()
{
cin >> n;
char tmp;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
{
cin >> tmp;
if (tmp == 'w')
orig[i][j] = 1;
}
}
void itop(int val, bool *isPressed)//将一个数字表示为二进制数填入数组中
{
for (int i = 0; i < n; i++)
if (val & (1 << i))
isPressed[i] = true;
else isPressed[i] = false;
}
inline void turn(int i, int j)//对第j行第k列的格子进行涂色操作
{
if (j > 0)//从第二行开始。i已经保证大于零了,所以不用判断
map[i][j-1] ^= true;//异或运算使对应格子颜色反转
map[i][j] ^= true;
if (j < n-1)
map[i][j+1] ^= true;
if (i < n-1)
map[i+1][j] ^= true;
}
void solve()
{
int ans = 15 * 15 + 50;
for (int i = 0; i < (1<<n); i++)//枚举第一行的所有可能方案
{
memcpy(map, orig, sizeof orig);
itop(i, res[0]); //将第一行转化成对应操作记录
for (int j = 0; j < n; j++)
if (res[0][j]) turn(0, j); //对图纸进行更新
for (int j = 1; j < n; j++)//逐行完善枚举结果
for (int k = 0; k < n; k++)//实施枚举结果,并传递信息给下一次
if (map[j-1][k])
res[j][k] = 1, turn(j, k);
else res[j][k] = 0; //对于多case的枚举,最好使用防御式编程,尽可能不要简省else
int flag = 1;
for (int j = 0; j < n; j++)
if (map[n-1][j])
flag = 0;//检查最后一行是否已经全部涂黄
if (flag) //如果有一个没有涂好,替代法一中的ptoi
{
int cnt = 0;
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
if (res[j][k]) cnt++;
if (cnt < ans)
ans = cnt;
}
}
if (ans < 275)
cout << ans;
else cout << "inf";
}
int main()
{
init();
solve();
}