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;
如果分开输入,那么输入之后,还要把字符串接起来,有没有什么办法直接得到整个字符串,偶然得到下面这个方法比较好使
输入之后,你再输出str试试char str[15]; scanf("%s%s%s%s", str, str + 4, str + 8, str + 12);
代码写得一般,凑合着看吧,我比较喜欢把代码写得短一点。
#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; }