題目鏈接:http://poj.org/problem?id=1753
題目大意:
翻棋子游戲,給定一個4*4的棋盤上擺放的初始狀態,棋子有黑白兩色,求將所有棋子翻成同一色的最少步數。規則:每次最多翻轉一個棋子和其上下左右共五個棋子,則最少三個,因爲四個角上的棋子在邊界上,有兩個鄰居不存在。若無法翻轉成同一個顏色則輸出 "IImpossible",初始狀態即爲目標狀態時輸出0。
題目思路:
棋子總共只有兩種顏色,則可用0或1表示當前狀態,共十六顆棋子則總共只有2^16=65536種狀態。
則我們可以用一個hash判重,將當前狀態一個十六位的二進制數轉換成十進制數標記。解決了狀態標記,判重問題,接下來就只需一個基礎的BFS即可!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 70000
int queue[MAX];
int step[MAX]; //記錄走到當前狀態的最少步數
int a[16];
int n,m;
void find(int x) //將一個十進制數轉換成二進制數
{
int i=16,k=0;
while (i--)
{
a[k]=x%2;
x=x/2;
k++;
}
}
int check() //將二進制數轉換成十進制數
{
int s=a[0],temp=1,i;
for (i=1;i<16;i++)
{
temp*=2;
s+=a[i]*temp;
}
return s;
}
void bfs()
{
int qe=0,qs=0,i,u;
queue[qe++]=n; //初始狀態入隊
step[n]=0;
while (qs<qe)
{
int temp=queue[qs++];
if (temp==0||temp==65535)
{
m=step[temp];
break;
}
find(temp);
for (i=0;i<16;i++) //每次最多有十六個狀態可擴展
{
int s,x,z,y;
s=i-4;
x=i+4;
z=i-1;
y=i+1;
a[i]=(a[i]+1)%2;
if (s>=0&&s<16) //判斷與當前棋子相鄰的上方的棋子能否翻轉
a[s]=(a[s]+1)%2;
if (x>=0&&x<16)
a[x]=(a[x]+1)%2;
if ((y>=0&&y<16)&&i%4!=3) //當前棋子在右邊界時,與其相鄰的右方的棋子不能翻轉
a[y]=(a[y]+1)%2;
if ((z>=0&&z<16)&&i%4!=0) //當前棋子在左邊界時,與其相鄰的左方的棋子不能翻轉
a[z]=(a[z]+1)%2;
//printf("%d %d %d %d\n",a[s],a[x],a[z],a[y]);
u=check();
//printf("%d\n",u);
if (step[u]==-1||(step[u]>step[temp]+1)) //判斷當前節點是否已擴展過,並且是否是最優。
{
queue[qe++]=u;
step[u]=step[temp]+1;
}
//將翻轉過的棋子復位,供下一次擴展
a[i]=(a[i]+1)%2;
if (s>=0&&s<16)
a[s]=(a[s]+1)%2;
if (x>=0&&x<16)
a[x]=(a[x]+1)%2;
if ((y>=0&&y<16)&&i%4!=3)
a[y]=(a[y]+1)%2;
if (z>=0&&z<16&&i%4!=0)
a[z]=(a[z]+1)%2;
//int t=check();
//printf("\n");
//printf("%d %d\n",u,step[u]);
}
}
}
int main()
{
//while (1)
//{
int i,j,s=1;
char c;
n=0; //記錄初始狀態的十進制數
//memset(flag,0,sizeof(flag));
memset(step,-1,sizeof(step));
for (i=0;i<4;i++)
{
for (j=0;j<4;j++)
{
if (i!=0||j!=0)
s*=2;
c=getchar();
if (c=='b')
{
n+=s*1;
}
else
{
n+=s*0;
}
//printf("%d\n",n);
}
getchar();
}
//printf("%d\n",n);
m=-1;
//system("pause");
bfs();
if (m!=-1)
printf("%d\n",m);
else
printf("Impossible\n");
//system("pause");
//}
return 0;
}