P3744智力游戏
问题描述
小A在玩一个简单的智力游戏,这个游戏是这样的:
在一个4*4的矩阵中分别有4个1,4个2,4个3和4个4。
每一步小A可以把同一行的4个数往左移或者往右移一步或者把同一列的4个数字往上移或者往下移一步(1,2,3,4往左移后是2,3,4,1)。
小A现在想知道最少几步移动可以使得矩阵的每行上的4个数字都一样或者每列上的4个数字都一样。但是小A又不想走太多步,他只要知道最少步数是否少于等于5步,是的话输出准确的步数,否则输出-1。
输入格式
一个4*4的矩阵
输出格式
一个整数,表示最优步数。无解输出-1
样例输入 1
1 2 3 4
1 2 3 4
1 2 3 4
2 3 4 1
样例输出 1
1
样例输入 2
4 1 1 1
1 2 2 2
2 3 3 3
3 4 4 4
样例输出 2
1
本题要用到迭代加深dfs+启发式搜索,重点在于估价函数的设计
我们设定估价函数: F(i)=G(i)+H(i) G(i)记录到达i棋盘状态所需步数 H(i)=min{ 所有行最少所需操作的次数总和,所有列最少所需操作的次数总和 }
这里的最少操作次数是对于单个一行/列来讲的,即这一行/列最少保证到达目标状态的操作次数
我们讨论将下面棋盘转换成合法目标棋盘需要的操作数:
1 1 3 2
2 4 4 4
3 3 1 2
1 2 3 4
水平方向:第1行至少2次,第2行至少1次,第3行至少2次,第4行至少3次,总次数至少8次
1 2 3 4
1 2 3 4
1 2 3 4
2 3 4 1
如上样例数据所示,水平方向总次数为3+3+3+3=12,垂直方向总次数为1+1+1+1=4 看起来调整垂直方向更合算。于是,我们将第四行右移一位,发现1次操作达到的最终目的。 而我们用上面方法估算出的次数至少是4。 矛盾的原因是,在理想情况下,我们通过一次移动,可以使得4个数字都到达合适的位置上。
于是,我们修改估价函数:
H(i)=(min{ 所有行最少所需操作的次数总和,所有列最少所需操作的次数总和 }+3)/4
这里+3即为向上取整
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int s[10][10],depth=1;
int geth(){
int temp1=0,temp2=0,i,j;
int cnt[10];
for(i=1;i<=4;i++){
int maxx=0;
memset(cnt,0,sizeof(cnt));
for(j=1;j<=4;j++){
cnt[s[i][j]]++;
maxx=max(cnt[s[i][j]],maxx);
}
temp1+=4-maxx;
}
for(j=1;j<=4;j++){
int maxx=0;
memset(cnt,0,sizeof(cnt));
for(i=1;i<=4;i++){
cnt[s[i][j]]++;
maxx=max(cnt[s[i][j]],maxx);
}
temp2+=4-maxx;
}
return (min(temp1,temp2)+3)/4;
}
void move_right(int x){
int i,y=s[x][4];
for(i=4;i>1;i--)s[x][i]=s[x][i-1];
s[x][1]=y;
}
void move_left(int x){
int i,y=s[x][1];
for(i=1;i<=3;i++)s[x][i]=s[x][i+1];
s[x][4]=y;
}
void move_up(int x){
int i,y=s[1][x];
for(i=1;i<=3;i++)s[i][x]=s[i+1][x];
s[4][x]=y;
}
void move_down(int x){
int i,y=s[4][x];
for(i=4;i>1;i--)s[i][x]=s[i-1][x];
s[1][x]=y;
}
bool dfs(int k){
int h=geth();
if(k+h>depth)return 0;
if(h==0)return 1;
int i;
for(i=1;i<=4;i++){
move_left(i);
if(dfs(k+1))return 1;
move_right(i);
move_right(i);
if(dfs(k+1))return 1;
move_left(i);
move_up(i);
if(dfs(k+1))return 1;
move_down(i);
move_down(i);
if(dfs(k+1))return 1;
move_up(i);
}
return 0;
}
int main(){
int i,j;
for(i=1;i<=4;i++)
for(j=1;j<=4;j++)cin>>s[i][j];
if(!geth()){
puts("0");
return 0;
}
for(depth=1;depth<=5;depth++)
if(dfs(0))break;
printf("%d",depth<=5?depth:-1);
}