http://poj.org/problem?id=1753
题意:
一个4×4的棋盘,每个格子放着一个棋子。棋子一面是白色,一面是黑色。一次操作可以将某一个格子以及上下左右共5个格子的棋子都翻过来,即白色变黑色,黑色变白色。现在给出一种棋盘状态,问最少需要几次操作可以将棋盘全部变为同种颜色。
输入:
Sample Input
bwwb bbwb bwwb bwww
Sample Output
4
典型的广搜题目:这里把这道题的收获写在这里,以便以后回顾:
首先是要明白SPFA算法:
算法的流程其实与分层遍历二叉树的代码差不多。
if (0 == t || (1<<16) - 1 == t) return 0;
memset(rec, 0, sizeof(rec)); memset(step, 0, sizeof(step));
queue<int> q; q.push(t); step[t] = 0; rec[t] = 1;
while (!q.empty())
{
t = q.front(); q.pop();
if (0 == t || (1<<16) - 1 == t) return step[t];
for (int i = 0; i < 16; ++i)
{
int next = t;
flip(next, (i >> 2), (i & 0x3));
if (0 == rec[next])
{
step[next] = step[t] + 1;
rec[next] = 1;
q.push(next);
}
}
}
return -1;flip的实现
inline void set(int &m, int i, int j)
{
int bit = (i << 2) + j;
m |= (1 << bit);
}
inline void clr(int &m, int i, int j)
{
int bit = (i << 2) + j;
m &= ~(1 << bit);
}
inline bool test(int &m, int i, int j)
{
int bit = (i << 2) + j;
return m & (1 << bit);
}
inline void rever(int &m, int i, int j)
{
if (test(m, i, j)) clr(m, i, j);
else set(m, i, j);
}
inline bool range(int i, int j)
{
if (i < 0 || i > 3 || j < 0 || j > 3) return false;
return true;
}
void flip(int &m, int i, int j)
{
if (false == range(i, j)) return;
rever(m, i, j);
const static int dk[4][2] = { {-1, 0}, {0, -1}, {0, 1}, {1, 0}};
for (int k = 0; k < 4; ++k)
{
int x = i + dk[k][0]; int y = j + dk[k][1];
if (false == range(x, y)) continue;
else rever(m, x, y);
}
}后来发现对某一位取反,只要 n ^= (1 << i );就可以了。
实际上,如果对于0,1,两位取异或则是 n ^= 3
那么在翻动的过程中,翻动第一个棋子时,应该与19取异或。
每一位应该与哪个数取异或可以由以下程序得到
#include<stdio.h>
int main(void)
{
const int dk[4][2] = { {-1, 0}, {0, -1}, {0, 1}, {1, 0}};
int i, j, k, n, v;
int x, y;
for (i = 0; i < 16; ++i)
{
n = (1<<i);
for (j = 0; j < 4; ++j)
{
x = (i>>2) + dk[j][0]; y = (i&0x3) + dk[j][1];
if (x < 0 || x > 3 || y < 0 || y > 3) continue;
else { v = (x << 2) + y; n |= (1 << v);}
}
printf("%d,", n);
}
return 0;
}
那么在在翻某一位时,可以把代码更改如下:
q[++tail] = t; rec[t] = 1;
while (head != tail)
{
t = q[++head];
for (i = 0; i < 16; ++i)
{
next = t; next ^= flip[i]; //直接由数组实现取异或。
if (0 == rec[next])
{
rec[next] = 1;
step[next] = step[t] + 1;
if (0 == next || (1<<16) - 1 == next) return step[next];
q[++tail] = next;
}
}
}
return -1;输入的处理
如果分开输入,那么输入之后,还要把字符串接起来,有没有什么办法直接得到整个字符串,偶然得到下面这个方法比较好使
char str[15];
scanf("%s%s%s%s", str, str + 4, str + 8, str + 12);输入之后,你再输出str试试
代码写得一般,凑合着看吧,我比较喜欢把代码写得短一点。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int flip[] = {19,39,78,140,305,626,1252,2248,4880,10016,20032,35968,12544,29184,58368,51200};
char rec[1<<16];
int step[1<<16];
int q[1<<18];
int BFS(int t)
{
int i, x, y, next, bit, k;
int head = -1, tail = -1;
if (0 == t || (1<<16) - 1 == t) return 0;
memset(rec, 0, sizeof(rec)); memset(step, 0, sizeof(step));
q[++tail] = t; rec[t] = 1;
while (head != tail)
{
t = q[++head];
for (i = 0; i < 16; ++i)
{
next = t; next ^= flip[i];
if (0 == rec[next])
{
rec[next] = 1;
step[next] = step[t] + 1;
if (0 == next || (1<<16) - 1 == next) return step[next];
q[++tail] = next;
}
}
}
return -1;
}
char str[40];
static int getn(char *s)
{
int iter = -1;
int n = 0;
for (; *s; ++s)
{
if ('b' != *s && 'B' != *s && 'w' != *s && 'w' != *s) continue;
if ('b' == *s || 'B' == *s) n |= (1 << ++iter);
}
return n;
}
int main(void)
{
int n, v, x, y;
scanf("%s%s%s%s", str, str + 4, str + 8, str + 12);
n = getn(str);
v = BFS(n);
if (-1 == v) printf("Impossible\n");
else printf("%d\n", v);
return 0;
}
本文介绍了解决4×4棋盘中黑白棋子最少翻转次数的问题,通过广搜算法实现,详细阐述了SPFA算法、flip函数实现以及输入输出处理方法,并提供了代码优化实例。
2726

被折叠的 条评论
为什么被折叠?



