华容道
Posted 夕時弥月風
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了华容道相关的知识,希望对你有一定的参考价值。
先扔个代码了……之后再写详细点吧
感谢@Erutsiom 巨佬的解读题目
dalao题解链接
//by Saber Alter Official
//Erishikigal & Ishtar
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
//#define Kama
typedef long long ll;
typedef unsigned long long ull;
const int N = 35, inf = 0x3f3f3f3f;
int n, m, q, ex, ey, sx, sy, tx, ty;
bool G[N][N], OK[N][N][5];//OK[i][j][k]表示绿色格子在(i,j)位置的k方向是否能有空白格子
int mv[4][2] = {{-1, 0},{1, 0},{0, -1},{0, 1}};//0上 1下 2左 3右
/*建边操作*/
struct node {
int to, next, val;
}edge[5015];
int head[5015], cnt;
inline void add_edge(int u, int v, int val) {
edge[++cnt].to = v;
edge[cnt].next = head[u];
edge[cnt].val = val;
head[u] = cnt;
}
/*建边结束*/
/*合法性检验*/
inline bool Check(int x, int y) {
if(x <= 0 || y <= 0 || x > n || y > m || !G[x][y]) return false;
return true;
}
/*编号函数第一次看比较难理解
举个例子,我们让(1,1)格子四个状态为0123
(1,2)格子四个状态是4567, 以此类推
就可以得到一个横行里相同状态比上一列多4。
再假设一共五列,(2,1)就是20.21.22.23,比上一行相同列相同状态多了4*m个
所以可以推出x上的运算应该是要乘以m,为了让第一行出现0123,要先把x变成(x-1)
然后加上y,最外面乘4,就可以得到上面的倍数关系,最后加上dir-4就可以了
*/
inline int SetNum(int x, int y, int dir) {
return (((x - 1) * m + y) * 4 + (dir - 4));
}
struct Vacancy {
int x, y, step;
};
std::queue<Vacancy> Que;
inline void Clear(std::queue<Vacancy> &Que) {
std::queue<Vacancy> Empty;
std::swap(Que, Empty);
}
//不能通过fbdx,fbdy从stx,sty到tarx, tary
inline int BFS(int fbdx, int fbdy, int stx, int sty, int tarx, int tary) {
//初始化
Clear(Que);
bool vis[N][N];
memset(vis, 0, sizeof vis);
//初始化队头
Vacancy head;
head.x = stx;
head.y = sty;
head.step = 0;//起步是0哦不要打成1
Que.push(head);
while(!Que.empty()) {
Vacancy pos = Que.front();
Que.pop();
if(pos.x == tarx && pos.y == tary) {
return pos.step;
}
if(vis[pos.x][pos.y]) continue;
vis[pos.x][pos.y] = true;
for(int k = 0;k < 4;k++) {
Vacancy New;
New.x = pos.x + mv[k][0];
New.y = pos.y + mv[k][1];
if(Check(New.x, New.y)) {//新位置本身合法
if(vis[New.x][New.y]) continue;//不符合两种条件
if(New.x == fbdx && New.y == fbdy) continue;
New.step = pos.step + 1;
Que.push(New);
}
}
}
return inf;//队列跑空了也找不到
}
void init() {
scanf("%d %d %d", &n, &m, &q);
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= m;j++) {
scanf("%d", &G[i][j]);
}
}
#ifdef Kama
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= m;j++) {
printf("%d%c", G[i][j], j == m ? '\n' : ' ');
}
}
#endif
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= m;j++) {
if(!G[i][j]) continue;//本身就不能走动
for(int k = 0;k < 4;k++) {
if(Check(i + mv[k][0], j + mv[k][1]))//绿格子和空白格子的移动处理
OK[i][j][k] = true;//预处理移动,看这个位置向哪里移动合法
}
}
}
//空格绕着绿色格子乱动的距离
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= m;j++) {//i,j枚举绿色格子位置
for(int k = 0;k < 4;k++) {
for(int p = k + 1;p < 4;p++) {//空白格在各个位置尝试移动
if(OK[i][j][k] && OK[i][j][p]) {//k=1 p=2.3.4 //k=2 p=3.4//k=3 p=4就枚举了所有情况
int _knum = SetNum(i, j, k);//给格子编号,用来存在图里
int _pnum = SetNum(i, j, p);//一样啦
int _step = BFS(i, j, i + mv[k][0], j + mv[k][1], i + mv[p][0], j + mv[p][1]);
if(_step == inf) continue;//不可能到达
add_edge(_knum, _pnum, _step);//建边!
add_edge(_pnum, _knum, _step);
}
}
}
}
}
//空格和绿色格子打包移动,也就是不断交换位置来移动
//这是左右来回推进
for(int i = 1;i <= n;i++) {
for(int j = 1;j < m;j++) {
if(OK[i][j][3] && OK[i][j + 1][2]) {//j在左边,右有空格
int _lefNum = SetNum(i, j + 1, 2);//j+1在右边,左有空格
int _rigNum = SetNum(i, j, 3);
add_edge(_lefNum, _rigNum, 1);
add_edge(_rigNum, _lefNum, 1);
}
}
}
//上下,意义同理啦
for(int i = 1;i < n;i++) {
for(int j = 1;j <= m;j++) {
if(OK[i][j][1] && OK[i + 1][j][0]) {//上面的下边有空白格,下面的上面有空白格
int _upNum = SetNum(i, j, 1);
int _dowNum = SetNum(i + 1, j, 0);
add_edge(_upNum, _dowNum, 1);
add_edge(_dowNum, _upNum, 1);
}
}
}
}
int dis[5005];//dis开小了!!!!!!!!!!!!!!!!!!!!!!!!!
std::queue<int> que;
bool vis[5005];
inline void Solve() {
memset(dis, 0x3f3f3f3f, sizeof dis);
memset(vis, 0, sizeof vis);
#ifdef Kama
for(int i = 1;i <= 100;i++) {
printf("%d\n", dis[i]);
}
#endif
for(int k = 0;k < 4;k++) {
if(Check(sx + mv[k][0], sy + mv[k][1])) {//枚举空白格到绿色格子周围的情况
int nowdis = BFS(sx, sy, ex, ey, sx + mv[k][0], sy + mv[k][1]);
if(nowdis == inf) continue;
int nownum = SetNum(sx, sy, k);
que.push(nownum);
dis[nownum] = nowdis;
#ifdef Kama
printf("dis:%d\n", dis[nownum]);
#endif
vis[nownum] = true;
}
}
//上面的循环是针对每次询问,先让空白格跑到绿色格子周围,完成一个初始化
//接下来,因为我们已经得到的绿色和空白格子在一起的位置编号
//又已经建好了这个棋盘上所有绿色和空白格子一起互达的图,就可以跑spfa了!
while(!que.empty()) {
int u = que.front();
que.pop();
vis[u] = false;
for(int i = head[u];i;i = edge[i].next) {
int v = edge[i].to;
if(dis[v] > dis[u] + edge[i].val) {
dis[v] = dis[u] + edge[i].val;
#ifdef Kama
printf("cdis:%d %d\n", dis[v], dis[u]);
#endif
if(!vis[v]) {
#ifdef Kama
printf("v:%d\n", v);
#endif
que.push(v);
vis[v] = true;
}
}
}
}
/*最后检查一下答案就可以了*/
int ans = inf;
for(int k = 0;k < 4;k++) {
int tarnum = SetNum(tx, ty, k);
ans = std::min(ans, dis[tarnum]);
#ifdef Kama
printf("tar:%d\n", tarnum);
printf("dis:%d%c", dis[tarnum], k == 3 ? '\n' : ' ');
#endif
}
if(ans == inf) printf("-1\n");
else {
printf("%d\n", ans);
}
}
void Query() {
for(int i = 1;i <= q;i++) {//空白格信息 绿色格子信息 终点信息
scanf("%d %d %d %d %d %d", &ex, &ey, &sx, &sy, &tx, &ty);
if(sx == tx && sy == ty) {//本身在终点
printf("0\n");
continue;
}
else if(!G[sx][sy]) {//起点不合法
printf("-1\n");
continue;
}
else if(!G[tx][ty]) {//终点不合法
printf("-1\n");
continue;
}
Solve();
}
}
int main() {
init();
Query();
return 0;
}
以上是关于华容道的主要内容,如果未能解决你的问题,请参考以下文章