八个数码八重境界,说的很有道理。你们要是有精力可以把所有的都试一下 我干不动了 到这里就退了哈哈哈。
如果没有做过1043的建议先去做一下1043.
HDU1043
附上1043的题解1043题解
话不多说了来看一下题意。和1043很像 如果没做过1043可以做一下。区别就是 1043更简单,1043说的是问一个状态要去一个固定的状态,反向bfs打表就行了。而这个题,要给你一个初始状态和一个最终状态让你求解初始状态到最终状态的最短步数,而且要输出路径(最dt的是要求输出的路径是字典序最小的那个)就是因为这个字典序我卡了将近一天。拿到题目我一看两个状态我心想这双向bfs不是稳了吗。就赶紧写。写完之后一发上去TML,然后我就懵了。
这里补充一下关于这个字典序路径最短我最初想的是根据bfs特性就直接按照字典序搜索就行了(其实是有点问题的)。
然后TML之后我就参考了别人的双向bfs,说是因为存储路径用的string多次复制使得时间超了,哇 ,找到原因了改代码吧。参照别人的思想使用了状态压缩的方法用四进制数来存储路径(不得不说大佬就是大佬,思维是真的活跃)。
两个公式:
正向:now.path*4+i (这个状态是由那个状态推过来的就那个状态就是now,就是你对那个状态做了 那个操作变成了这个状态,i分别为0,1,2,3分别表示四种操作)
反向:(3 -i)x pow4[now.step]+now.path(now状态和上面一样,pow4数组就是4的次方数组,看代码会懂,step就是走到now走了多少步)。
有了这两个公式就可以存路径了哈哈哈,我可以a啦,额额额,但是又是一发wa,心态有点崩了呀,但这里就出来了一个问题,就是我上面说的输出路径字典序最小的问题,我以为从前从后按字典序最小来搜索就可以保证字典序其实不是。
**正向字典序最小+反向字典序最小不等于整体字典序最小。**这就话大家可以好好理解一下。所以每次找到一条到这个点的路径不要着急退出去一定要与同层的节点比较去字典序最小的那个。到了这一步不出什么细节问题应该可以a了。一天半时间就这样过去了。
细节看一下代码注释吧。
双向bfs代码:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=362890;
const int INF=1e8;
const int fac[10]={1,1,2,6,24,120,720,5040,40320,362880};
int go[8]={1,0,0,-1,0,1,-1,0};
char index[5]="dlru";
ll pow4[30];
int vis[2][maxn],Min_ans;
ll c[2][maxn];
struct node{
int sta[9]; //存状态的数组
int lac; //X的位置
int hash; //康拓展开hash函数值
int flag; //标记是正向还是反向 0为正 1为反
int step; //到这一步走了多少步
ll path; //路径保存在里面
};
void init(){
pow4[0]=1;
for(int i=1;i<30;i++){
pow4[i]=pow4[i-1]*4;
}
}
int cantor(int *a){ //康拓展开
int x=0;
for(int i=0;i<9;i++){
int small=0;
for(int j=i+1;j<9;j++){
if(a[j]<a[i])small++;
}
x+=fac[9-i-1]*small;
}
return x+1;
}
string get_str(ll a,int flag,int kk){ //将路径转换为字符串
int str[100],cnt=0;
for(int i=1;i<=vis[flag][kk];i++){
str[++cnt]=a%4;
a/=4;
}
string ans="";
for(int i=cnt;i>0;i--){
ans+=index[str[i]];
}
return ans;
}
void bfs(node bb,node ee){
queue<node>q;
memset(vis,-1,sizeof(vis));
//memset(c,0,sizeof(c));
node now,temp;
bb.hash=cantor(bb.sta);
bb.path=0;bb.flag=0;bb.step=0;
q.push(bb);
ee.hash=cantor(ee.sta);
ee.path=0;ee.flag=1;ee.step=0;
q.push(ee);
vis[0][bb.hash]=0;
vis[1][ee.hash]=0;
Min_ans=INF;
ll str;
string res;
if(bb.hash==ee.hash){
printf("0\n\n");
return ;
}
while(!q.empty()){
now=q.front();
q.pop();
int x=now.lac/3;
int y=now.lac%3;
for(int i=0;i<8;i+=2){
int xx=go[i]+x;
int yy=go[i+1]+y;
if(xx<0||yy<0||xx>=3||yy>=3)continue;
temp=now;
temp.lac=xx*3+yy;
swap(temp.sta[temp.lac],temp.sta[now.lac]);
temp.hash=cantor(temp.sta);
if(vis[now.flag][temp.hash]!=-1){ //根据标记有个方向已经搜索到这个点了,将其变为字典序最小的那个
if(now.step+1>vis[now.flag][temp.hash])continue;
else{
if(now.flag)str=(3-i/2)*pow4[now.step]+now.path;
else str=now.path*4+i/2;
if(c[now.flag][temp.hash]>str){
c[now.flag][temp.hash]=str;
}
}
}
else{ //没到这个点(不论正向反向)
vis[now.flag][temp.hash]=now.step+1;
if(now.flag) c[now.flag][temp.hash]=(3-i/2)*pow4[now.step]+now.path;
else c[now.flag][temp.hash]=now.path*4+i/2;
}
temp.step++;
temp.path=c[now.flag][temp.hash];
if(vis[now.flag^1][temp.hash]!=-1){ //正向反向都到了有结果了
//cout<
//cout<
//cout<
//cout<
string s=get_str(c[0][temp.hash],0,temp.hash)+get_str(c[1][temp.hash],1,temp.hash);
//cout<
int slen=s.length();
if(slen>Min_ans){
cout<<Min_ans<<endl;
cout<<res<<endl;
return ;
}
else if(slen<Min_ans){
Min_ans=slen;
res=s;
}
else{
if(res.compare(s)>0)res=s;
}
}
q.push(temp);
}
}
}
int main()
{
int t,cnt=0;
init();
scanf("%d",&t);
while(t--){
char b[10],e[10];
node bb,ee;
scanf("%s %s",b,e);
for(int i=0;i<9;i++){
if(b[i]=='X'){
bb.lac=i;
bb.sta[i]=0;
}
else bb.sta[i]=b[i]-'0';
if(e[i]=='X'){
ee.lac=i;
ee.sta[i]=0;
}
else ee.sta[i]=e[i]-'0';
}
printf("Case %d: ",++cnt);
bfs(bb,ee);
}
return 0;
}
再讲一下第二种思路:单项bfs打表。
单向bfs就简单很多了我就没有费那么多的时间去写了,但是整体的思路还是要和大家 讲一下的,
单向bfs就没有必要考虑那么多的情况了,就是单纯的按照字典序搜索结束之后的路径就是字典序最小的那个。
打表的话就要有一个固定的思维模式,我们将X的不同位置分为了九种情况,
char s[9][10]={
"X12345678","1X2345678","12X345678","123X45678","1234X5678","12345X678",
"123456X78","1234567X8","12345678X"
};
分别对这九种情况进行一下搜索所有的情况都会转换成相应的其中一种,
我们以题目的第一个样例为例子看一下。
样例本身 1 2 X 4 5 3 7 8 6
对应样例 1 2 X 3 4 5 6 7 8
这时候你就把4看成3 ,5看成4, 3看成5 ,7看成6, 8看成7,6看成8,然后依次对应到输出结果里面直接找到相应的hash值答应结果即可。
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=4e5;
const int fac[10]={1,1,2,6,24,120,720,5040,40320,362880};
int go[8]={1,0,0,-1,0,1,-1,0};
char index[5]="dlru";
char s[9][10]={
"X12345678","1X2345678","12X345678","123X45678","1234X5678","12345X678",
"123456X78","1234567X8","12345678X"
};
int vis[9][maxn];
int per[9][maxn];
struct node{
int sta[9]; //状态数组
int lac; //x的位置
int hash; //hash值
};
int cantor(int *a){
int x=0;
for(int i=0;i<9;i++){
int small=0;
for(int j=i+1;j<9;j++){
if(a[j]<a[i])small++;
}
x+=small*fac[9-i-1];
}
return x;
}
void bfs(int k,int lin){
node now,temp;
int a[10];int v=1;
for(int j=0;j<9;j++){
if(j==lin)a[j]=0;
else a[j]=v++;
now.sta[j]=a[j];
}
now.hash=cantor(a);
now.lac=lin;
queue<node>q;
q.push(now);
vis[k][now.hash]=1;
while(!q.empty()){
now=q.front();
q.pop();
int x=now.lac/3;
int y=now.lac%3;
for(int i=0;i<8;i+=2){
int xx=x+go[i];
int yy=y+go[i+1];
if(xx>=0&&yy>=0&&xx<=2&&yy<=2){
temp=now;
temp.lac=xx*3+yy;
swap(temp.sta[temp.lac],temp.sta[now.lac]);
temp.hash=cantor(temp.sta);
if(vis[k][temp.hash]==-1){
vis[k][temp.hash]=i/2;
per[k][temp.hash]=now.hash;
q.push(temp);
}
}
}
}
}
int main()
{
memset(vis,-1,sizeof(vis));
memset(per,-1,sizeof(per));
for(int i=0;i<9;i++){
bfs(i,i);
}
int t,cnt=0;
scanf("%d",&t);
while(t--){
char b[10],e[10];
scanf("%s %s",b,e);
int bb[10],ee[10],k,v=1;
for(int i=0;i<9;i++){
if(b[i]=='X'){
bb[i]=0;
k=i;
}
else{
bb[i]=v++;
}
}
for(int i=0;i<9;i++){
if(e[i]=='X'){
ee[i]=0;
continue;
}
for(int j=0;j<9;j++){
if(e[i]==b[j]){
ee[i]=bb[j];
break;
}
}
}
int ans=cantor(ee);v=0;
string str="";
while(ans!=-1){
str+=index[vis[k][ans]];
ans=per[k][ans];
}
int slen=str.length();
printf("Case %d: %d\n",++cnt,slen-1);
for(int i=slen-2;i>=0;i--){
cout<<str[i];
}
printf("\n");
//cout<
}
return 0;
}