AOJ 0121: Seven Puzzle (BFS DP STL 逆向推理)

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0121

题意:7数码问题。在2×4的棋盘上,摆有7个棋子,每个棋子上标有1至7的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格(用0表示),与空格相邻(上下左右)的棋子可以移到空格中,该棋子原先位置成为空格。给出一个初始状态(保证可以转移到最终状态),找出一种从初始状态转变成给定最终状态的移动棋子步数最少的移动步骤。

输入:多组输入,每组8个数,表示初始状态前四个数为第一行从左到右,后四个数为第二行从左到右。 

输出:至少需要多少步可以从输入状态到达最终状态(0 1 2 3 4 5 6 7) 

(图1)
  (图2)
  (图3)
分析:
乍一看这题没从入手,但只要采取逆向思维,还是可以用广度优先搜索解决。先不考虑如何用最小步数从输入状态到达最终状态,所有结果的最终状态都是(01234567),那么反过来想,只要求出最终状态到达所有结果时的最小步数并记录下来,接下来就是查表了。0表示空位置,对空位置周围的格子采用广度优先的方式移动到0,并记录下最小步数的结果即可。如上图所示,图1,可以选择让7移动过来变成图2,也可以选择让2移动过来变成图3。我们要做的只不过是不断重复这种选择,直至穷举所有情况并记录结果。
我主要是用一个map<string, int>来表示(01234567)到string 的最小步数int,只要当前结果还不存在,就加入map,必然能够穷尽状态。另外,在移动格子问题上,因为我采用string来表示当前状态,那么移动方向上下左右分别就是当前位置-4, +4, -1, +1。需要注意的是,位置3不能移动到位置4.

思路:与前几题的bfs不同,这次的bfs没有明确的移动对象,看似任意一个数都可以当成对象移动。这时我们只需要抓住一个格子就行,比如我们把0作为移动对象,那么0在地图中漫游所有的格子得到的肯定就是问题的解空间。由于题目的输入是多个case,如果对每个case都运行一遍bfs就会TLE。这时我们祭出dp技能,只需要一次bfs就将解空间算出来,以后每个case只要到解空间中去找就行了。

#include <iostream>
#include <string>
#include <algorithm>
#include <map>
#include <queue>
using namespace std;
 
map<string, int> dp;
int direction[4] = { 1, -1, 4, -4 };
 
//************************************
// Method:    bfs
// FullName:  bfs
// Access:    public 
// Returns:   void
// Qualifier: 让0漫游整个字串
//************************************
void bfs()
{
    queue<string> que;
    que.push("01234567");
    dp["01234567"] = 0;
    while (!que.empty())
    {
        string now = que.front(); que.pop();
        // p是'0'的位置
        int p = 0;
        for (int j = 0; j < 8; ++j)
        {
            if (now[j] == '0')
            {
                p = j;
                break;
            }
        }
 
        for (int i = 0; i < 4; ++i)
        {
            int n = p + direction[i];
            if (0 <= n && n < 8 && 
                !(p == 3 && i == 0) && // 右上角不能再往右了
                !(p == 4 && i == 1))   // 左下角不能再往左了
            {
                string next = now;
                swap(next[p], next[n]);
                if (dp.find(next) == dp.end())
                {
                    dp[next] = dp[now] + 1;
                    que.push(next);
                }
            }
        }
    }
}
 
///SubMain//
int main(int argc, char *argv[])
{
 
    bfs();
    string line;
    while (getline(cin, line))
    {
        line.erase(remove(line.begin(), line.end(), ' '), line.end());
        cout << dp[line] << endl;
    }
 
    return 0;
}
///End Sub//

代码二

#include <iostream>
#include <queue>
#include <map>
#include <string>
#include <algorithm>

using namespace std;

typedef pair<string, int> P;

const int INF = 100000000;

//输入
string a;

//移动方向
int op[4] = {-1, 1, -4, 4};

map<string, int> dp;                        //保存从string变到"01234567"的int

//计算从"01234567"转换到其他序列所需的最小步数
void bfs(){
    //初始化
    queue<P> que;
    que.push(P("01234567", 0));
    dp["01234567"] = 0;
    //宽度优先搜索
    while(!que.empty()){
        P p = que.front();
        que.pop();
        string s = p.first;
        int cur = p.second;
        for(int i = 0; i < 4; i ++){
            //构造下一次交换
            int next = cur + op[i];
            string str = s;
            swap(str[cur], str[next]);
            map<string, int>::iterator it = dp.find(str);
            //判断是否可移动以及是否访问过
            if(0 <= next && next < 8 
                && !(cur == 3 && next == 4) && !(cur == 4 && next == 3) 
                && it == dp.end()){

                que.push(P(str, next));
                dp[str] = dp[s] + 1;
            }
        }
    }
}

void solve(){
    //删除空格
    a.erase(remove(a.begin(), a.end(), ' '), a.end());
    cout<<dp[a]<<endl;
}

int main(int argc, char const *argv[]){
    //先逆向构造所有情况,后直接读取输入用例的结果
    bfs();
    while(getline(cin, a)){
        solve();
    }
    return 0;
}

用find函数来定位数据出现位置,它返回的一个迭代器, 当数据出现时,它返回数据所在位置的迭代器, 如果map中没有要查找的数据, 它返回的迭代器等于end函数返回的迭代器

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值