题目来源:

http://poj.org/problem?id=1077

题目大意:

给你一个由1到8和x组成的3*3矩阵,x每次可以上下左右四个方向交换。求一条路径,得到12345678x这样的矩阵。若没有路径,则输出unsolvable。

经典的八数码问题。

这题我用A*算法做的。推荐一篇博客,从大体上介绍了一下启发式算法的代表A*算法:

https://www.cnblogs.com/zhoug2020/p/3468167.html

首先就是判重的问题,搜索的状态是九个数(含x),开个九重数组也不是不可以,但用cantor展开hash一下还是方便的。所谓cantor展开,就是对1..n的所有排列,唯一对应一个1..n!的值。

cantor展开解决互不相同的元素的全排列问题(互不相同就可以了)。代码中的cantor展开,可以当做模板用。

康托展开的公式是 X=a1*(n-1)!+a2*(n-2)!+...+ai*(i-1)!+...+an-1*1!+an*0! 其中,ai为当前元素比之后多少个元素大。

其实理解公式很简单,给你排列2,4,1,5,3,你可以这么想,第一个放2,说明1开头的都排完了,至少比4!种排列靠后了,第二个数是4,说明2,1开头和2,3开头的都排过了,又比2*3!种排列靠后了。以此类推。。

其次就是奇偶剪枝了。奇偶剪枝是一个常见的预判断的方法。接下来详细阐述一下:

1、逆序数的概念

给定数列a[1..n],记LESS(i)为a[i+1]到a[n]中比a[i]小的数的个数,则逆序数为∑LESS(i)。比如有数列a[1..5]=[3,7,2,19,2],则LESS(1)到LESS(5)分别为2,2,0,1,0,所以逆序数为2+2+0+1+0=5。

2、树状数组(线段树)求逆序数

将值val与序号id放在结构体中一起排序,先按值的升序排列,值相同时,再按序号升序排列(这样后面求LESS值的时候不会把相同的数算进去)。

比如a[1..5]=[3,7,2,19,2],排序成[2:3,2:5,3:1,7:2,19:4](冒号后为排序用结构体里的id)。

然后维护[1..n]的树状数组。初始全为0,顺序遍历排好序的结构体,将id处的值变为1,并查询i+1到n的和,即可得到LESS值,再将LESS值求和,即可。

比如上例顺序执行可得LESS(3)=0,LESS(5)=0,LESS(1)=2,LESS(2)=2,LESS(4)=1。逆序数为5。

3、八数码的奇偶剪枝

对于1..n的排列,交换相邻的元素,逆序数奇偶性一定改变。所以如果将八数码中的x视作9的话,八数码可以看成是1..9的排列。左右交换,是一次相邻元素的交换,上下交换,可以看成3+2=5次相邻交换,逆序数奇偶性一定改变。

现在我们改变一下1中对LESS(i)的定义,重定义为对于数i,LESS(i)为从它的位置到最后比它小的数的个数。由于现在的排列没有相同的数,所以求和得到的逆序数是不变的。

如果从1..9的排列中除去9,可以发现1..8的LESS值不变,而上下/左右交换前后LESS(9)的奇偶性一定改变,又结合之前交换前后逆序数奇偶性改变,得到1..8逆序数奇偶性不变。

还有一个15迷的问题,奇偶剪枝也是一样的分析思路,只是加了个辅助量。

最后就是Astar算法本身了:

首先需要一个优先队列。由于对优先级的需要,我们需要在结构体中对<运算符进行重载(详见代码)。

然后结构体中存储路径最好还是用string吧,毕竟vector还是没它方便。

好像就是这么多。搜索这种东西,复杂度比较难把握,所以能优化的地方尽量优化一下吧。比如搜索方向,感觉还是先搜索down和right比较好,虽然可能效果不太明显。

这道题还要继续做。hdu1043还没A掉,双向BFS,还有什么打表方法还没有尝试写呢。加上!!!,表示要记得做!

#include<iostream>
#include<cstdio>
#include<string>
#include<queue>
#include<cstring> using namespace std; char buf[];
int puz[]; int fac[]={,,,,,,,,,};
int cantor(int s[])
{
int sum=;
for(int i=;i<;i++)
{
int num=;
for(int j=i+;j<;j++)
if(s[j]<s[i])
num++;
sum+=(num*fac[-i-]);
}
return sum+;
} struct tnode
{
int puz[];
string path;
int loc;//0的位置
int status;//cantor展开值
int n;//搜索深度
int f;//估值函数
int getloc()
{
for(int i=;i<;i++)
if(puz[i]==)
return i;
}
bool operator<(const tnode& y) const
{
return f>y.f;
}
};
int vis[];
int dis[]={,,,,,,,,};
int a[]={,,-,-};
char b[]="rdlu"; int aim=; int main()
{
while(scanf("%[^\n]",buf)!=EOF)
{
getchar(); for(int i=,cnt=;buf[i]!='\0';i++)
{
if(buf[i]=='x')
puz[cnt++]=;//用0存储x
else if(buf[i]>=''&&buf[i]<='')
puz[cnt++]=buf[i]-'';
}
//for(int i=0;i<9;i++) printf("%d ",puz[i]); printf("\n");
//printf("%d\n",cantor(puz)); int cnt=;
for(int i=;i<;i++)
if(puz[i]!=)
{
for(int j=i+;j<;j++)
if(puz[j]!=&&puz[j]<puz[i])
cnt++;
}
if(cnt%==)
{
printf("unsolvable\n");
continue;
} priority_queue<tnode> q;
memset(vis,,sizeof(vis));
tnode node1;
memcpy(node1.puz,puz,sizeof(puz));
node1.path="";
node1.loc=node1.getloc();
node1.status=cantor(node1.puz);
node1.n=;
node1.f=node1.n+dis[node1.loc];
if(node1.status==aim)//入队之前先判断,可以加快速度
{
cout<<node1.path<<endl;
continue;
}
q.push(node1);
vis[node1.status]=;
bool flag=false;
while(!q.empty())
{
tnode node2=q.top();q.pop();
bool fflag=false;
for(int i=;i<;i++)
{
tnode node3=node2;
//printf("%d ",node3.loc);
if(a[i]==&&node3.loc%<=)
{
node3.puz[node3.loc]=node3.puz[node3.loc+a[i]];
node3.puz[node3.loc+a[i]]=;
node3.status=cantor(node3.puz);
}
else if(a[i]==-&&node3.loc%>=)
{
node3.puz[node3.loc]=node3.puz[node3.loc+a[i]];
node3.puz[node3.loc+a[i]]=;
node3.status=cantor(node3.puz);
}
else if(a[i]==&&node3.loc<=)
{
node3.puz[node3.loc]=node3.puz[node3.loc+a[i]];
node3.puz[node3.loc+a[i]]=;
node3.status=cantor(node3.puz);
}
else if(a[i]==-&&node3.loc>=)
{
node3.puz[node3.loc]=node3.puz[node3.loc+a[i]];
node3.puz[node3.loc+a[i]]=;
node3.status=cantor(node3.puz);
}
//printf("%d ",node3.status);
if(!vis[node3.status])
{
node3.path+=b[i];
node3.loc=node3.getloc();
node3.n++;
node3.f=node3.n+dis[node3.loc];
q.push(node3);
vis[node3.status]=;
}
if(node3.status==aim)
{
node1=node3;
fflag=true;
break;
}
}
if(fflag)
{
flag=true;
break;
}
} if(flag)
{
cout<<node1.path<<endl;
}
else
{
printf("unsolvable\n");
}
}
return ;
}

poj 1077 Eight (八数码问题——A*+cantor展开+奇偶剪枝)的更多相关文章

  1. hdu3567 八数码2(康托展开+多次bfs+预处理)

    Eight II Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 130000/65536 K (Java/Others)Total S ...

  2. POJ 1077 Eight (BFS+康托展开)详解

    本题知识点和基本代码来自<算法竞赛 入门到进阶>(作者:罗勇军 郭卫斌) 如有问题欢迎巨巨们提出 题意:八数码问题是在一个3*3的棋盘上放置编号为1~8的方块,其中有一块为控制,与空格相邻 ...

  3. BFS(八数码) POJ 1077 || HDOJ 1043 Eight

    题目传送门1 2 题意:从无序到有序移动的方案,即最后成1 2 3 4 5 6 7 8 0 分析:八数码经典问题.POJ是一次,HDOJ是多次.因为康托展开还不会,也写不了什么,HDOJ需要从最后的状 ...

  4. Eight POJ - 1077 HDU - 1043 八数码

    Eight POJ - 1077 HDU - 1043 八数码问题.用hash(康托展开)判重 bfs(TLE) #include<cstdio> #include<iostream ...

  5. Poj 1077 eight(BFS+全序列Hash解八数码问题)

    一.题意 经典的八数码问题,有人说不做此题人生不完整,哈哈.给出一个含数字1~8和字母x的3 * 3矩阵,如: 1  2  X            3 4  6            7  5  8 ...

  6. CDOJ 485 UESTC 485 Game (八数码变形,映射,逆cantor展开)

    题意:八数码,但是转移的方式是转动,一共十二种,有多组询问,初态唯一,终态不唯一. 题解:初态唯一,那么可以预处理出012345678的所有转移情况,然后将初态对012345678做一个映射,再枚举一 ...

  7. poj 1077-Eight(八数码+逆向bfs打表)

    The 15-puzzle has been around for over 100 years; even if you don't know it by that name, you've see ...

  8. POJ 2893 M × N Puzzle——八数码有解条件

    题意:给定M*N的数码图,问能否移动到最终状态 分析 有解的判定条件可见 八数码有解条件 值得一提的是,这道题求逆序对卡树状数组,只能用归并排序. #include<cstdio> #in ...

  9. hdu 1043 pku poj 1077 Eight (BFS + 康拓展开)

    http://acm.hdu.edu.cn/showproblem.php?pid=1043 http://poj.org/problem?id=1077 Eight Time Limit: 1000 ...

随机推荐

  1. 微信小程序-rpx

    rpx 是微信小程序解决自适应屏幕尺寸的尺寸单位.微信小程序规定屏幕的宽度为750rpx. 微信小程序同时也支持rem尺寸单位, rem 规定屏幕的宽度为20rem, 所以 1rem = (750/2 ...

  2. Java开发中常用jar包整理及使用

    本文整理了我自己在Java开发中常用的jar包以及常用的API记录. <!-- https://mvnrepository.com/artifact/org.apache.commons/com ...

  3. echarts绘制彩虹色背景

    大致成品如图所示 关键的步骤: var dom = document.getElementById("myChart"); var myChart = echarts.init(d ...

  4. JSAPI 基于arcgis_js_api3.3的部署

    JSAPI,即ArcGIS API For JavaScript,是arcgis基于JavaScript环境下的开发包.包含Dojo框架. ||Dojo结构如下:=================== ...

  5. php使用QueryList轻松采集JavaScript动态渲染页面

    QueryList使用jQuery的方式来做采集,拥有丰富的插件. 下面来演示QueryList使用PhantomJS插件抓取JS动态创建的页面内容. 安装 使用Composer安装: 安装Query ...

  6. odoo12 修行基础篇之 添加明细字段 (二)

    前一篇介绍了如何在视图和表单中添加字段.本节内容,我们讨论下如何在明细中加字段. 我想在销售页面明细中增加税额字段,这在表sale.order.line中已经存在,在此仅用来演示. odoo的明细一般 ...

  7. day 07 复习总结

    今日主要内容 1. 补充基础数据类型的相关知识点 1. str. join() 把列表变成字符串 对应的是split () 表示把字符串变成列表.  ()里面为分隔符,不写默认为空格分隔 1.吧 2. ...

  8. Virtualbox修改虚拟机分配内存的大小

    起因:因为虚拟机刚开始分配的内存太小,导致太卡, 解决方法:修改虚拟机分配内存的大小 方法一:必须在关闭ubuntu的前提下进行,否则无法修改 点击设置 系统选项 主板中的内存大小 之后开启即可 方法 ...

  9. 构建调试Linux内核网络代码的环境MenuOS系统

    构建MenuOS系统 1.将指定文件拷贝到本地: git clone https://github.com/mengning/linuxnet.git 此过程可能需要输入github账号和密码. 2. ...

  10. vsftpd架设(配置pam模块)

    Vsftpd 是很安全的ftp软件 VSFTPD的目录结构 /usr/sbin/vsftpd: VSFTPD的可执行文件 /etc/rc.d/init.d/vsftpd:启动脚本 /etc/vsftp ...