广度搜索-POJ 1753

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;
}




   

你可能感兴趣的:(算法,output)