hdu6171 Admiral

题意:给你一个如图形状21个元素的排列,只能够通过0元素跟其上下相邻的元素进行交换,问是否可以在20步内转换成目标排列,如果可以输出最小步数,否则输出too difficult

这是目标排列,给定的初始排列可能不同
图片说明

分析

首先可以看出这是一个求最短路的题,我们可以把元素的排列看成是一种状态,他可以经过若干次状态转移到达目标状态,这就是一个最短路问题了。

尝试使用普通BFS

0元素可以向四个方向移动,上左,上,下,下右,极端情况走到21步(也就是too difficult),一共有4^21的复杂度,1e12的数量级了,普通bfs必定会超时。

尝试使用双向BFS

让初始排列和目标排列一起扩展,极端情况,双方各扩展10步,也就是两个4^10,2e6的数量级,然后在双方扩展的过程中,看有没有交集。如果有交集就判断是否是20步以内,如果两个队头状态的步数相加已经超过20步了,也要结束循环。

状态怎么存储

如果我们把这个排列数组存下来,可见非常消耗内存,可以通过哈希的方式存储,可以将排列转换成字符串,也可以将排列用自建哈希函数,因为一个只有21个数,每个数是0~5,只占用3个二进制位,一共需要占用63个二进制位,而longlong有64个二进制位,所以其状态可以用一个longlong来存储。

双向BFS+string存储

#include <iostream>
#include <algorithm>
#include <string>
#include <queue>
#include <unordered_map>
using namespace std;
const int maxn = 1e6+10;
typedef pair<int,int> pii;
int T,sx,sy;
string a,b;
unordered_map<string,int> disa,disb;
unordered_map<string,pii> posa,posb;
queue<string> qa,qb;

int extentA(){
    string cur = qa.front();qa.pop();
    int d = disa[cur];
    pii p = posa[cur];
    int x = p.first,y = p.second;
    int nx = -1,ny = -1;
    for(int i = 0;i<4;i++){
        if(i == 0) nx = x-1,ny = y;
        if(i == 1) nx = x-1,ny = y-1;
        if(i == 2) nx = x+1,ny = y;
        if(i == 3) nx = x+1,ny = y+1;
        if(nx>=1 && nx<=6 && ny>=1 && ny<=nx){
            swap(cur[x*(x-1)/2+y],cur[nx*(nx-1)/2+ny]);
            if(!disa[cur]){
                qa.push(cur);
                disa[cur] = d+1;
                posa[cur] = {nx,ny};
                if(disb.count(cur)) return disa[cur] + disb[cur];
            }
            swap(cur[x*(x-1)/2+y],cur[nx*(nx-1)/2+ny]);
        }
    }

    return -1;
}
int bfs(){
    disa.clear();disb.clear();
    posa.clear();posb.clear();
    while(qa.size()) qa.pop();
    while(qb.size()) qb.pop();
    qa.push(a);qb.push(b);
    disa[a] = 0,disb[b] = 0;
    posa[a] = {sx,sy};posb[b] = {1,1};

    while(qa.size() && qb.size()){
        if(disa[qa.front()] + disb[qb.front()] > 20) return -1;
        if(qa.size() <= qb.size()){ //谁目前扩展的少就去扩展谁
            int res = extentA();
            if(res!=-1 && res<=20) return res;
            if(res>20) return -1;
        }else{ //扩展B时,交换一下指针就好
            swap(qa,qb);
            swap(disa,disb);
            swap(posa,posb);
            int res = extentA();
            if(res!=-1 && res<=20) return res;
            if(res>20) return -1;
            swap(qa,qb);
            swap(disa,disb);
            swap(posa,posb);
        }
    }

    return -1;
}

int main(){
    cin>>T;
    while(T--){
        a = '-';b = "-011222333344444555555";//以字符串存储状态,下标从1开始
        char c;
        for(int i = 1;i<=6;i++){
            for(int j = 1;j<=i;j++){
                scanf(" %c",&c);
                a+=c;
                if(c == '0')
                    sx = i,sy = j;
            }
        }
        if(a == b) puts("0");
        else{
            int res = bfs();
            if(res == -1) puts("too difficult");
            else cout<<res<<endl;
        }

    }


    return 0;
}

双向BFS+Hash

#include <iostream>
#include <algorithm>
#include <queue>
#include <unordered_map>
using namespace std;
typedef unsigned long long ull;
int dir[4][2] = {-1,0,-1,-1,1,0,1,1};
int T;
struct node{
    ull x,y;
    ull hs;
    int st[7][7];
}sta,stb;

ull Hash(node &no){
    ull hs = 0;
    for(int i =0;i<6;i++){
        for(int j = 0;j<=i;j++){
            hs = hs<<3|no.st[i][j];
        }
    }
    return hs;
}

int ex(queue<node> &q,unordered_map<ull,int> &mp1,unordered_map<ull,int> &mp2){
    node cur = q.front();q.pop();
    for(int i = 0;i<4;i++){
        ull dx = cur.x+dir[i][0];
        ull dy = cur.y+dir[i][1];
        if(dx<0 || dx>5 || dy<0 || dy> dx) continue;
        node ne = cur;
        swap(ne.st[cur.x][cur.y],ne.st[dx][dy]);
        ne.x = dx,ne.y = dy,ne.hs = Hash(ne);
        if(!mp1.count(ne.hs)) {
            mp1[ne.hs] = mp1[cur.hs]+1;
            q.push(ne);
            if(mp2.count(ne.hs)) return mp1[ne.hs] + mp2[ne.hs];
        }
    }
    return -1;
}

int bfs(){
    queue<node> qa,qb;
    unordered_map<ull,int> disa,disb;
    qa.push(sta);qb.push(stb);
    disa[sta.hs] = 0;disb[stb.hs] = 0;
    while(qa.size() && qb.size()){
        if(disa[qa.front().hs] + disb[qb.front().hs]>20) return -1;
        int res;
        if(qa.size()<= qb.size()){
            res = ex(qa,disa,disb);
        }else{
            res = ex(qb,disb,disa);
        }
        if(res!=-1 && res<=20) return res;
        if(res>20) return -1;//需注意
    }
    return -1;
}

int main(){
    cin>>T;
    while(T--){
        for(int i = 0;i<6;i++){
            for(int j = 0;j<=i;j++){
                scanf("%d",&sta.st[i][j]);
                stb.st[i][j] = i;
                if(sta.st[i][j] == 0){
                    sta.x = i,sta.y = j;
                }
            }
        }
        stb.x = 0,stb.y = 0;
        stb.hs = Hash(stb);
        sta.hs = Hash(sta);
        if(sta.hs == stb.hs) puts("0"); //初始状态也需要判断
        else{
            int res = bfs();
            if(res == -1) puts("too difficult");
            else cout<<res<<endl;
        }

    }
    return 0;
}