hdu1401 双向广搜 用长度为8的字符串记录一种棋局(第二版)

相比第一版增加了对原理的具体说明。

题目如下:

Solitaire is a game played on a chessboard 8x8. The rows and columns of the chessboard are numbered from 1 to 8, from the top to the bottom and from left to right respectively.

There are four identical pieces on the board. In one move it is allowed to:

> move a piece to an empty neighboring field (up, down, left or right),

> jump over one neighboring piece to an empty field (up, down, left or right).

There are 4 moves allowed for each piece in the configuration shown above. As an example let's consider a piece placed in the row 4, column 4. It can be moved one row up, two rows down, one column left or two columns right.

Write a program that:

> reads two chessboard configurations from the standard input,

> verifies whether the second one is reachable from the first one in at most 8 moves,

> writes the result to the standard output.

Output

The output should contain one word for each test case - YES if a configuration described in the second input line is reachable from the configuration described in the first input line in at most 8 moves, or one word NO otherwise.

我的方法最特别的一点在于用长度为8的字符串存储每一种棋局。

比如有一个棋局,四个点的坐标为(1,3),(1,1),(1,2)和(2,1)。

如何用字符串存储它?我们考虑先将这四个点按x坐标从小到大排序,x坐标相同的按y坐标从小到大排序,对应代码的cmp部分。这四个坐标排序后依次为(1,1),(1,2),(1,3),和(2,1)。用一个字符串记录这种棋局,相当于string s=(11121321)的字符形式。

这个棋局的下一步有哪些可能?我对每个棋子的四个方向都做判断(共计16次),如(1,1),它不能往(1,0)和(0,1)走,因为这两个坐标出棋盘界限了,而它在y轴正方向上有两个棋子(1,2)(1,3),以至于(1,1)既不能直接走到(1,2),也不能跳一步到(1,3)。(1,1)的x轴正方向有一个棋子(2,1),但(3,1)处没棋子了,所以(1,1)可以跳一步到(3,1)。这种情况下,四个点的坐标为(3,1),(1,2),(1,3),(2,1),排序后为(1,2),(1,3),(2,1),(3,1),判断这种情况另一个方向有没有搜到过,如果搜到过,那么输出yes,结束这个测试样例;如果同方向已经搜到过,那没有任何事。如果两个方向都没搜到过,那将这种棋局标记为自己方向,即vis[ string]=...

下面结合AC代码讲解

#include<iostream>
using namespace std;
#include<set>
#include<algorithm>
#include<cmath>
#include<map>
#include<cstdio>
#include<string>
#include<cstring>
#include<string.h>
#include<stdlib.h>
#include<iomanip>
#include<fstream>
#include<stdio.h>
#include<stack>
#include<queue>
#include<ctype.h>
#include<vector>
#define pb push_back
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,a,n) for(int i=n;i>=a;i--)
#define ull unsigned long long 
const int N = 1e6 + 10;
map<string, int> vis;//标记这个局面有没有出现过,等于1是正搜出现过,等于2是反搜出现过,等于0是没出现过
stack<string > st;//存入每一个搜过的字符串,等这个测试样例过了将这里面所有的字符串vis值清空,不影响下一组测试样例
queue<string> q1, q2, q3;
//q1是正搜的队列,q2是反搜的队列,q3是临时队列,存储每一轮搜出来的新的局面对应的字符串,这轮搜完存入q1或q2
int x[5], y[5];
string s1, s2;//记录初始棋局对应的字符串和目标棋局对应的字符串
int i, j;   
int x11, y11;
int dir[4][2] =
{
    {1,0},{-1,0},{0,1},{0,-1}
};//方向数组,对应上下左右四个方向
string ste;
bool flag;//记录每一个测试样例能不能达到,1为达到
struct node
{
    int x, y;
}s[5];//记录每一个棋局四个点的坐标
bool cmp(node a, node b)
{
    if (a.x != b.x)
    {
        return a.x < b.x;
    }
    return a.y < b.y;
}核心步骤,对每一个棋局四个点的坐标进行排序,先按x坐标排序;若x坐标相同,按y坐标排序。
    //一定要进行这个排序,确保每一个棋局只对应一种字符串
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    //FILE* stream;
    //freopen_s(&stream, "a.txt", "r", stdin);
    while (cin >> x[1] >> y[1] >> x[2] >> y[2] >> x[3] >> y[3] >> x[4] >> y[4])
    {
        s1.clear();//记得先清空存初始棋局的字符串
        s2.clear();
        rep(i, 1, 4)
        {
            s[i].x = x[i];
            s[i].y = y[i];
        }//先存储四个点的坐标
        sort(s + 1, s + 5, cmp);//排序
        rep(i, 1, 4)
        {
            s1 = s1 + char(s[i].x) + char(s[i].y);
            //注意,char(s[i].x)这个值是多少并不重要,只是一种存储的方法
            //不能写成s1 += char(s[i].x) + char(s[i].y);否则报错
        }
        cin >> x[1] >> y[1] >> x[2] >> y[2] >> x[3] >> y[3] >> x[4] >> y[4];
        rep(i, 1, 4)
        {
            s[i].x = x[i];
            s[i].y = y[i];
        }
        sort(s + 1, s + 5, cmp);
        rep(i, 1, 4)
        {
            s2 = s2 + char(s[i].x) + char(s[i].y);
        }
        q1.push(s1);
        vis[s1] = 1;
        q2.push(s2);
        vis[s2] = 2;
        st.push(s1);
        st.push(s2);
        flag = 0;
        rep(t,1,4)//题目要求八步以内到达,对应正搜反搜各四次
        {
            while (q1.size())
            {
                string stemp = q1.front();               
                q1.pop();
                for (int i = 1; i <= 4; i++)
                {
                    x[i] = stemp[2 * i - 2];
                    y[i] = stemp[2 * i - 1];
                } 
                rep(i, 1, 4)//对每个点讨论
                {
                    rep(j, 0, 3)//对这个点的每个方向讨论
                    {
                        x11 = x[i] + dir[j][0];//记录这个点在这个方向的下一个坐标值
                        y11 = y[i] + dir[j][1];                      
                        if (x11 < 1 || x11>8 || y11 < 1 || y11>8)
                        {
                            aa:
                            continue;
                        }
                        bool flag1 = 0;
                        //设置flag1是为了防止这个点在这个方向前面有两个棋子,这种情况它不能往这个方向移动
                        tt:
                        rep(k, 1, 4)
                        {
                            if (x[k] == x11 && y[k] == y11)//判断这个方向前面有没有棋子
                            {
                                if (flag1 == 1)//对应在这个方向前面有两个棋子的情况
                                {
                                    goto aa;
                                }
                                x11 += dir[j][0];
                                y11 += dir[j][1];
                                flag1 = 1;//对应在这个方向前面有一个棋子的情况,继续判断这个点前面还有没有棋子
                                goto tt;
                            }
                        }
                        ste.clear();
                        int cnt = 0;
                        rep(k, 1, 4)
                        {
                            cnt++;
                            if (k != i)
                            {
                                s[cnt].x = x[k];
                                s[cnt].y = y[k]; 
                            }
                            else
                            {
                                s[cnt].x = x11;
                                s[cnt].y = y11;
                            }
                        }
                        sort(s + 1, s + 5, cmp);//存储前先排个序
                        rep(k, 1, 4)
                        {
                            ste =ste+ char(s[k].x) +char(s[k].y);
                        }
                        if ( vis[ste] == 2)//这个棋局被反搜搜过了,那么这个测试样例是有解的
                        {
                            flag = 1; 
                            goto jiesuan;//学会用goto,很方便快捷
                        }
                        else if(vis[ste]==0)
                        {
                            vis[ste] = 1;
                            q3.push(ste);
                            st.push(ste);
                        }
                    }
                }
            }          
            while (q3.size())
            {
                q1.push(q3.front());
                q3.pop();
            }
            while (q2.size())
            {
                string stemp = q2.front();
                q2.pop();
                for (int i = 1; i <= 4; i++)
                {
                    x[i] = stemp[2 * i - 2] ;
                    y[i] = stemp[2 * i - 1] ;
                }     
                rep(i, 1, 4)//对每个点讨论
                {
                    rep(j, 0, 3)//对每个方向讨论
                    {
                        x11 = x[i] + dir[j][0];
                        y11 = y[i] + dir[j][1];                   
                        if (x11 < 1 || x11>8||y11<1||y11>8)
                        {
                            tttt:
                            continue;
                        }
                        bool flag2 = 0;
                        aaa:
                        rep(k, 1, 4)
                        {
                            if (x[k] == x11 && y[k] == y11)
                            {
                                if (flag2 == 1)
                                {
                                    goto tttt;
                                }
                                x11 += dir[j][0];
                                y11 += dir[j][1];
                                flag2 = 1;
                                goto aaa;
                            }
                        }
                        ste.clear();
                        int cnt = 0;
                        rep(k, 1, 4)
                        {
                            cnt++;
                            if (k != i)
                            {
                                s[cnt].x = x[k];
                                s[cnt].y = y[k];
                            }
                            else
                            {
                                s[cnt].x = x11;
                                s[cnt].y = y11;
                            }
                        }
                        sort(s + 1, s + 5, cmp);
                        rep(k, 1, 4)
                        {
                            ste =ste+ char(s[k].x) + char(s[k].y);
                        }
                        if (vis[ste] == 1)
                        {
                            flag = 1;
                            goto jiesuan;
                        }
                        else if(vis[ste]==0)
                        {
                            vis[ste] = 2;
                            q3.push(ste);
                            st.push(ste);
                        }
                    }
                }
            }
            while (q3.size())
            {
                q2.push(q3.front());
                q3.pop();
            }
        }
        jiesuan:
        if (flag)
        {
            cout << "YES\n";
        }
        else
        {
            cout << "NO\n";
        }
        while(st.size())//清零标记值,为下一个测试样例提供初始环境
        {
            vis[st.top()] = 0;
            st.pop();
        } 
        while (q1.size())//加一道保险,为下一个测试样例提供初始环境
        {
            q1.pop();
        }
        while (q2.size())
        {
            q2.pop();
        }
        while (q3.size())
        {
            q3.pop();
        }
    }
}

谢谢阅读。如有不当之处,欢迎批评指正。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值