相比第一版增加了对原理的具体说明。
题目如下:
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();
}
}
}
谢谢阅读。如有不当之处,欢迎批评指正。