NKOI 3744 智力游戏

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次,第2列至少3次,第3列至少2次,第4列至少2次,总次数至少9次

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





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值