15年(上)周赛2(2015-03-15) Problem A Aizu 0121

To 学弟学妹们,经过仔细阅读,首先理解了题目是要我们求给定一个2*4的带号码的方格,每次可以交换 0和相邻的数,问给定起始状态,最少需要多少步交换可以达到目标状态 (不要问我为什么日语这么屌)

0 1 2 3

4 5 6 7

典型的八数码问题,这里用到bfs求出所有解,说一下通常解法。

从起点状态开始,上下左右扩展所有可能情况,若遇到最终状态,则输出。

怎样判断一个状态之前已经扩展过,避免死循环?这里我给出一些特殊的技巧。(更深的知识还有很多探讨的地方,大家可以关注我5月份的讲座)

由于是判断一个全排列是否出现过

1、这里可以利用康拓展开,将全排列一一映射到一个数字,比如

0 1 2 3 4 5 6 7 --> 1

0 1 2 3 4 5 7 6 --> 2

具体的如何映射,包括原理可以自行百度 康托展开,或者看代码。【这里的映射是一一映射,所以不需要用到Hash表,我下面的代码这里多写了,大家无视就好】

2、也可以利用Hash,设置一个Hash函数,将序列Hash成一个整数,经过模运算存在数组里,例如∑a[i]*2^i。

【Hash表的相关操作大家可以看我刚刚叫大家无视的部分】

3、或者利用C++自带的STL中的map,将序列定义为String字符串,然后直接利用map判重。


但是这题由于终点状态是固定的,并且输入很多(1000组),那么一组一组计算的话,同样会很慢,所以我们还需要另一个技巧,那就是反向搜索,一次性保存所有状态的答案。也就是做一次预处理,那么对于后面的输入就可以直接输出了。


代码写的太挫,实在不想粘贴上来,姑妄写之,姑妄看之


#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<iostream>
#include<cmath>
#include<map>
#include<queue>
using namespace std;  
struct node
{
	int mp[2][4];
	int dis;
	int x,y;
}f,t;
int FLAG;
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
bool isok(int x,int y)
{
	return x>=0&&x<2&&y>=0&&y<4;
}
int ca[]={1,1,2,6,24,120,720,5040,40320,362880}; 
int ANS[1000007];
int cantor(node &t)  
{  
    int sum=0,cnt=0;  
    int s[9];
    for(int i=0;i<2;i++)
    {
    	for(int j=0;j<4;j++)
    	{
    		s[cnt++]=t.mp[i][j];
    	}
    }
    for(int i=0;i<8;i++)  
    {  
   	 int num=0;  
        for(int j=i+1;j<8;j++)  
          if(s[j]<s[i]) num++;  
        sum+=(num*ca[8-i-1]);  
    }  
    return sum+1;  
}  
const int MAXN=1000007;
int Hash[MAXN];
const int END=1;
bool HashInsert(int value,int dis)  //就是这里 ,无视它
{
      int v=value%MAXN;
      while(Hash[v]!=-1&&Hash[v]!=value){
          v+=10;
          v%=MAXN;
      }
      if(Hash[v]==-1){
          ANS[v]=dis;
          Hash[v]=value;
         return true;
      }
      return false;
  }
bool isvis(node &t,int dis)
{
	int val=cantor(t);
	if(val==END) FLAG=1;
	return HashInsert(val,dis);
}
bool over(node &t)
{
	int cro=0;
	for(int i=0;i<2;i++)
	{
		for(int j=0;j<4;j++)
		{
			if(t.mp[i][j]!=cro) return false;
			cro++;
		}
	}
	return true;
}
void bfs()
{
	FLAG=0;
	
	queue<node> q;
	q.push(f);
	while(!q.empty())
	{
		f=q.front();q.pop();
		for(int d=0;d<4;d++)
		{
			t=f;
			t.x=f.x+dx[d];
			t.y=f.y+dy[d];
			if(isok(t.x,t.y))
			{
				t.dis++;
				swap(t.mp[f.x][f.y],t.mp[t.x][t.y]);
				if(isvis(t,t.dis))	//标记 
				{
					q.push(t);
				}
			}
		}
	}
}
int main()
{memset(Hash,-1,sizeof(Hash));
	f.dis=0;
	int cnt=0;
	for(int i=0;i<2;i++)
		{
			for(int j=0;j<4;j++)
			{
				f.mp[i][j]=cnt++;
			}
		}
	f.x=0;
	f.y=0;
	Hash[1]=1;
	ANS[1]=0;
	bfs();
	while(~scanf("%d",&f.mp[0][0]))
	{
		f.dis=0;
		for(int i=1;i<=3;i++) scanf("%d",&f.mp[0][i]);
		for(int i=0;i<=3;i++) scanf("%d",&f.mp[1][i]);
		printf("%d\n",ANS[cantor(f)]);
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TommyTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值