POJ 1753 Flip Game

最近开始写POJ上面的题目,网上搜了份推荐题目开始做。

POJ-1753链接

大意是4*4的黑白棋,每次可以选择一个棋子将其和上下左右5个棋子翻转(边界情况特殊些),问最少几次能全黑或全白。

首先分析状态,4*4的棋盘,估计状态不会很多,同时对于一个棋子,翻转奇数次或者偶数次的情况是一样的,要么翻一次,要么不翻。

所以最多需要翻转的次数是16次,从1次到16次,一共有

\[ \dbinom{1}{16}+ \dbinom{2}{16}+...+ \dbinom{15}{16}+ \dbinom{16}{16} = 2^{16} \]

同时,我们可以只用一个数字来记录当前棋盘的状态,每个棋子非黑即白,可以用0、1来表示,棋盘状态可以被一个16位的二进制数代替。

算法大致思路,首先枚举翻转的次数,从1开始。然后在从棋盘左上角往右下角进行DFS搜索,如果当前棋子要翻转,则把该当前状态二进制数与该棋子对应的二进制位进行异或。到达条件是棋盘是否为0或者2^16-1

代码如下,其实可以不用定义数组,以及可能可以记录状态减少时间,不过懒得改了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <cstdio>
using namespace std;
const int goal = (1<<16)-1;
const int dpos[] = {-4, -1, 1, 4};
int ans = 0, num = 0;
int a[6][6];
int vis[5000];

void init()
{
char ch;
int res = 0;
for (int i = 1; i <= 4; i++)
{
for (int j = 1; j <= 4; j++)
{
scanf("%c", &ch);
if (ch == 'b') a[i][j] = 1, res++;
else a[i][j] = 0;
num = (num<<1)+a[i][j];
}
scanf("\n");
}
if (res == 16 || res == 0) ans = -1;
}

int search(int now, int pos, int cnt)
{
int flag = 0;
int npos, nxt = now;
if (cnt == 0)
{
if (now == 0 || now == goal)
return 1;
return 0;
}
if (pos + cnt > 17 || pos > 16) return 0;
for (int i = 0; i < 4; i++)
{
npos = dpos[i] + pos;
if (pos%4==0 && npos%4==1) continue;
if (pos%4==1 && npos%4==0) continue;
if (npos >= 1 && npos <= 16)
nxt ^= (1<<(16-npos));
}
nxt ^= (1<<(16-pos));
flag = search(nxt, pos+1, cnt-1);
if (flag == 1)
return 1;
flag = search(now, pos+1, cnt);
return flag;
}

void work()
{
int cnt;
if (ans == -1)
return (void) printf("0\n");
ans = 0;
for (cnt = 1; cnt <= 16; cnt++)
{
ans = search(num, 1, cnt);
if (ans != 0) break;
}
if (ans == 0) printf("Impossible\n");
else printf("%d\n", cnt);
}

int main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
init();
work();
return 0;
}