zoukankan      html  css  js  c++  java
  • 【FJ夏令营】八数码难题(洛谷P1379)

    题目描述

    在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

    输入格式

    输入初始状态,一行九个数字,空格用0表示

    输出格式

    只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)

    输入输出样例

    输入 #1
    283104765
    输出 #1
    4
     
    没参考szm大神的代码是不可能的,在此感谢szm。
    本题要用dfs迭代加深+IDA*+康托展开。为什么要用这个?是我强制自己用的,其实就是想练练手,其它dalao肯定还会有各种各样的解法
     
    ①迭代加深
    这个相信大家都比较熟悉了,对于求“最少需要几步”的问题比较常见。
    具体操作:从0开始枚举深度,要求dfs搜索树不能超过此深度。若dfs完成但没有找到解,则加 大 深 度,继续dfs,直到在某一个深度找到了解,就可以直接输出该深度了
    ②IDA*
    Oh my star
    IDA*适用于dfs+迭代加深,通过估价函数来对dfs进行优化。这个估价函数要尽量接近实际,但不能超过实际。对于本题而言,估价函数就是除0外的每个数字的当前位置与目标位置的曼哈顿距离之和。
    本题不用IDA*,会T得一塌糊涂。
    ③康托展开
    康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时压缩空间。每一种排列都可以表示为一个小于等于n!的正整数。而如果按照(n+1)进制处理,则需要的空间为(n+1)的(n+1)次幂。所以康托展开可以大幅优化空间。
    具体操作:X=a[n]*(n-1)!+a[n-1](n-2)!+...+a[1]*0!(其中a[i]表示第i位的数在未出现的数中排第几个)
    按照以上思路,形成了以下代码:
    #include<bits/stdc++.h>
    using namespace std;
    int a[4][4];
    int ans[4][4]=
    {{0,0,0,0},
    {0,1,2,3},
    {0,8,0,4},
    {0,7,6,5},
    };//目标状态
    int x_[10],y_[10];
    int movex[4]={0,1,0,-1},movey[4]={1,0,-1,0};//4个方向
    bool b[3700000];
    int fac[15]={1,1,2,6,24,120,720,5040,40320,362880};//阶乘,factorial,不是骂人
    int ida_()//IDA*,估值函数
    {
    	int res=0;
    	for(int i=1;i<=3;i++)
    	{
    		for(int j=1;j<=3;j++)
    		{
    			if(a[i][j])	res+=(abs(x_[a[i][j]]-i)+abs(y_[a[i][j]]-j));//求当前位置到目标位置的曼哈顿距离
    		}
    	}
    	return res;
    }
    bool f[10];
    int kt_()//康托
    {
    	int res=0;
    	memset(f,0,sizeof(f));
    	for(int i=1;i<=9;i++)
    	{
    		int x=(i-1)/3+1,y=(i-1)%3+1;
    		int z=0;
    		for(int j=0;j<a[x][y];j++) if(!f[j]) z++;//统计第几小
    		res+=z*fac[9-i];
    		f[a[x][y]]=1;//已经出现过的数,标记
    	}
    	return res;
    }
    bool res=0; 
    void dfs(int x,int y,int k,int step)//(x,y)定位当前“0”的位置,k为限定深度,step为当前深度
    {
    	if(step>k) return;//超过限定深度,返回
    	if(!ida_()){res=1;return;}//所有数字都到达目标位置,即曼哈顿距离之和为0,找到解
    	for(int i=0;i<4;i++)//周围四个方向的数字可以往空格移动
    	{
    		int xx=x+movex[i],yy=y+movey[i];
    		if(xx>=1&&xx<=3&&yy>=1&&yy<=3)
    		{
    			swap(a[x][y],a[xx][yy]);//移动,即与“0”交换位置
    			int kt=kt_();//求当前状态的逆康托展开
    			if(b[kt]||step+ida_()>k)//若当前状态已出现过,或当前状态最小步数肯定无法满足要求,返回
    			{
    				swap(a[xx][yy],a[x][y]);
    				continue;
    			}
    			b[kt]=1;//标记
    			dfs(xx,yy,k,step+1);
    			if(res) return;//若找到解,则返回,搜索结束
    			b[kt]=0;
    			swap(a[xx][yy],a[x][y]);//别忘了回溯
    		}
    	}
    }
    int main()
    {
    	for(int i=1;i<=3;i++)
    	{
    		for(int j=1;j<=3;j++) x_[ans[i][j]]=i,y_[ans[i][j]]=j;//预处理,存储目标状态下每个数字的坐标
    	}
    	int sx,sy;
    	for(int i=1;i<=9;i++)
    	{
    		char c=getchar();
    		int x=(i-1)/3+1,y=(i-1)%3+1,z=c-'0';
    		a[x][y]=z;
    		if(z==0) sx=x,sy=y;//确定“0”的位置
    	}
    	int kt=kt_();
    	b[kt]=1;//标记初始状态
    	for(int i=0;;i++)//迭代加深
    	{
    		dfs(sx,sy,i,0);
    		if(res)//若找到解,则输出,程序结束
    		{
    			printf("%d",i);
    			return 0;
    		}
    	}
    }
    

      

  • 相关阅读:
    神奇的条件注解-Spring Boot自动配置的基石
    Spring 注解配置原理
    元注解之@Repeatable
    MyBatis批量操作
    MapperScannerConfigurer源码解析
    Spring包扫描机制详解
    SqlSessionTemplate源码解析
    DataSourceUtils源码分析
    Spring事务源码分析
    多核CPU
  • 原文地址:https://www.cnblogs.com/dong-ji-yuan/p/12499503.html
Copyright © 2011-2022 走看看