UVa 1601 || POJ 3523 The Morning after Halloween (BFS || 双向BFS && 降维 && 状压)
题意 :w*h(w,h≤16)网格上有n(n≤3)个小写字母(代表鬼)。要求把它们分别移动到对应的大写字母里。每步可以有多个鬼同时移动(均为往上下左右4个方向之一移动),但每步结束之后任何两个鬼不能占用同一个位置,也不能在一步之内交换位置。输入保证所有空格连通,所有障碍格也连通,且任何一个2*2子网格中至少有一个障碍格。输出最少的步数。输入保证有解。
分析 :可以将当前所有小鬼的位置作为一个状态,然后模拟小鬼移动BFS拓展出其他可行状态并且顺带记录步数,直到所有的小鬼都到达终点。首先状态如何表示才能更方便去进行广搜?刚刚说了状态就是三个小鬼的位置!存储状态(也就是三个二维坐标)要结合判重和状态转移等广搜要进行的步骤来考虑,这里使用的是将三个二维坐标映射成一位数,原因请往下看,这里先顺带说一下,以前遇到的BFS大多都是一个起始一个终止,但是这里有多个小鬼,也就是多个起点多个终点,由于这里小鬼之间的位置会有互相影响的情况,所以自然不能分开来BFS找各自的最短路径再进行相加,广搜状态的定义直接影响到判重和状态拓展,对于判重,我们这里可能最多需要记录三个小鬼的位置状态。有了状态,面对最短路问题自然考虑判重,如何将状态存储以便进行判重呢?直接用结构体装起来再丢到set里面?直接开个高维度的vis数组(三个二维坐标六个数嘛,讲道理可以开个6维数组进行判重)?那空间复杂度肯定是爆炸的。既要保证时间又要保证空间,所以考虑降维操作,我们可以使用某种方法将二维的坐标映射成一个整数使用一个初始化全为-1的dis[][][]数组来表示三个小鬼的位置(即状态),同样用这个来记录此状态下的步数,这样就可以顺利进行判重和状态转移了。||| 再者虽然时限给的很大,但是如果纯BFS搜的话根据小道消息还是会TLE,这里为了避免每一次进行状态拓展的时候频繁地进行判断出界和往四个方向搜,直接先预处理每一个点,看这个点能到达那些点,当有小鬼在这个点需要状态转移的时候就能直接找到可走区域,不用判来判去,这里的记录方法是使用类似邻接表的一种结构,开个数组Go[][]和一个长度数组deg[],Go[i][deg[0]~deg[i]]表示 i 这个点的各个可行区,总共有deg[0]到deg[i]这么多(刚刚说过,已经对二维的坐标进行了降维操作,我们可以通过 i 找到原始的二维坐标,所以降维还给我们的可行区域预处理带来了很大的便利) ||| 还有就是刚刚上面的大体思路里面有个将二维的坐标进行映射变成一维的操作,直接使用数组row[]和col[]来保存二维坐标的行列,用变量cnt来保存坐标个数,也就是映射的函数值,每加一个就进行 row[cnt]=行, col[cnt]=列, cnt++;操作即可,不过还有一个问题,那如果有三个小鬼的话,装进队列的即使降维也是有三个整数,开个结构体也行,但是这里有一个更省空间的方法,就是采用二进制,比如进来三个小鬼的坐标a, b, c,根据我们刚刚映射的规则和w,h<=16,我们可以知道a,b,c<=16*16,用八位二进制就能表示出来,那就用一个24位的二进制来将这三个八位二进制保存起来,即(a<<16)|(b<<8)|c,就变成了一个整数,装进队列里面,取出来的时候就进行逆向分解就OK,即a = (queue.front()>>16)&0xff,b = (queue.front())>>8)&0xff, c = queue.front()&0xff。||| 细节就大概如此,为了更快可以进行双向BFS,即从终点和从起点分别搜,直到搜到双方都搜过的点,那权值相加就是答案了,可以参考=》 http://www.cppblog.com/Yuan/archive/2011/02/23/140553.aspx
单向BFS:
#include<bits/stdc++.h> using namespace std; ; int ROW, COl, Lnum; ; ][], blank[][]; ], en[]; , en_top = ; ][] = { {,},{,},{-,},{,-},{,} }; ]; ][]; ]; ]; ][][]; inline int Get_ID(int a, int b, int c)//->learn { )|(b<<)|c; } inline bool check(int fir_st, int sec_st, int fir_en, int sec_en)//->learn { return fir_en==sec_en || (fir_en==sec_st && sec_en==fir_st); } inline int bfs() { ], st[], st[]); memset(dis, -, sizeof(dis)); dis[st[]][st[]][st[]] = ; queue<int> q; q.push(u); while(!q.empty()){ int top = q.front(); q.pop(); //int a = top>>16, b = top>>8, c = top; )&)&0xff, c = top&0xff;//->Learn ] && b==en[] && c==en[]) return dis[a][b][c];//->Learn the dis ; i<deg[a]; i++){ int a2 = Go[a][i]; ; j<deg[b]; j++){ int b2 = Go[b][j]; if(check(a, b, a2, b2)) continue; ; k<deg[c]; k++){ int c2 = Go[c][k]; if(check(a, c, a2, c2)) continue; if(check(b, c, b2, c2)) continue; ) continue;//->learn dis[a2][b2][c2] = dis[a][b][c] + ;//这里实际上也是和每一个状态存一个权值是一样的,这里注意bfs找最短路时候的状态可以像dfs那样子走过不走 //但是这种状态式的就要想办法记录状态来避免重复情况 q.push(Get_ID(a2,b2,c2)); } } } } ; } int main(void) { while(~scanf("%d%d%d", &COl, &ROW, &Lnum) && (COl&&ROW&&Lnum)){ getchar(); cnt = ;//caution ; i<ROW; i++){ gets(G[i]); ; j<COl; j++){ if(G[i][j]!='#'){ blank[i][j] = cnt; row[cnt] = i, col[cnt] = j; if(islower(G[i][j])) st[G[i][j]-'a'] = cnt; else if(isupper(G[i][j])) en[G[i][j]-'A'] = cnt; cnt++; } } } //for(int i=0; i<cnt; i++) printf("%d %d\n", row[i], col[i]); ; i<cnt; i++){ deg[i] = ; ; j<; j++){ ]; ]; &&y<ROW) && (x>=&&x<COl)){ Go[i][deg[i]++] = blank[y][x];//blank use } } } ){//->Learn have not influence deg[cnt] = ; st[] = en[] = cnt; Go[cnt][] = cnt++; } ){ deg[cnt] = ; st[] = en[] = cnt; Go[cnt][] = cnt++; } printf("%d\n", bfs()); } ; }
双向BFS(参考=》 http://blog.csdn.net/qq_34446253/article/details/51346622 但是我对其进行了步数的改动,保证是一层层解决):
#include<cstdio> #include<cstring> #include<cctype> #include<queue> using namespace std; ; ; struct Seed{ int u, step; }; ],t[]; ]; //G[][]是邻接表保存,deg[i]保存第i个点有几个相连点 int dis[maxn][maxn][maxn],dis_back[maxn][maxn][maxn]; //判重同时也记录距离 int ID(int a,int b,int c) //三位数变为一位数记录 { )|(b<<)|c; //0|1=1; } bool conflict(int a,int b,int a2,int b2) { return b2==a2||(a==b2&&b==a2); //不能穿过和重合 } int bfs(queue<Seed>& q,int d[maxn][maxn][maxn], int step) { ){ Seed Top = q.front(); if(Top.step!=step) break; q.pop(); int u = Top.u; )&)&0xff, c = u&0xff; &&dis_back[a][b][c]!=-) return dis[a][b][c]+dis_back[a][b][c]; // 当遇到正反搜索都搜过的节点就表明已经找到解了 ; i < deg[a]; i++) { int a2 = G[a][i]; ; j < deg[b]; j++) { int b2 = G[b][j]; if(conflict(a, b, a2, b2)) continue; ; k < deg[c]; k++) { int c2 = G[c][k]; if(conflict(a, c, a2, c2)) continue; if(conflict(b, c, b2, c2)) continue; ) continue; d[a2][b2][c2] = d[a][b][c]+; Seed temp; temp.step = step+, temp.u=ID(a2,b2,c2); q.push(temp); } } } } ; } int solve() { Seed fir, fir_back; fir.step=, fir.u=ID(s[],s[],s[]); queue<Seed> q; q.push(fir); memset(dis,-,sizeof(dis)); dis[s[]][s[]][s[]]=; queue<Seed> q_back; fir_back.step=, fir_back.u=ID(t[],t[],t[]); q_back.push(fir_back); //终点出发 memset(dis_back,-,sizeof(dis_back)); dis_back[t[]][t[]][t[]]=; ; int ans; ) //正着搜一个节点,反着搜一个节点 { ans=bfs(q,dis,step); ) return ans; ans=bfs(q_back,dis_back,step); ) return ans; step++; } ; } int main() { int w,h,n; &&n) { getchar(); //读取一个回车 ][]; ;i<h;i++) fgets(maze[i],,stdin); //比scanf("%c",&maze[i][j])方便或者用cin>>maze[i]但比较慢; ,x[maxn],y[maxn],id[maxs][maxs]; ;i<h;i++) ;j<w;j++) { char ch=maze[i][j]; if(ch!='#') { x[cnt]=i;y[cnt]=j; //记录每个编号的横纵坐标,方便等下记录与其连接的节点 id[i][j]=cnt; //对每个非'#'节点编号,cnt表示编号 if(islower(ch)) s[ch-'a']=cnt; else if(isupper(ch)) t[ch-'A']=cnt; cnt++; } } ,-,, ,}; //可以走五步 , ,,-,}; ;i<cnt;i++) { deg[i]=; ;dir<;dir++) { int nx=x[i]+dx[dir]; int ny=y[i]+dy[dir]; if(maze[nx][ny]!='#') G[i][deg[i]++]=id[nx][ny]; //G[i][0~deg[i]-1]是保存从i点出发可到的点; } } ) deg[cnt]=,G[cnt][]=cnt,s[]=t[]=cnt++; //对于不到3个的,新建一个点,起点终点在同一位置且该点只能到该点,即自环 ) deg[cnt]=,G[cnt][]=cnt,s[]=t[]=cnt++; printf("%d\n",solve()); } ; }
刘汝佳源码:
// UVa1601 The Morning after Halloween // Rujia Liu // This code implements the simpliest yet efficient-enough algorithm I'm aware of // Readers are encouraged to experiment on other algorithms (especially for better efficiency!) #include<cstdio> #include<cstring> #include<cctype> #include<queue> using namespace std; ; ; // 75% cells plus 2 fake nodes ,-,,,}; // 4 moves, plus "no move" ,,,-,}; inline int ID(int a, int b, int c) { )|(b<<)|c; } ], t[]; // starting/ending position of each ghost ]; // target cells for each move (including "no move") inline bool conflict(int a, int b, int a2, int b2) { return a2 == b2 || (a2 == b && b2 == a); } int d[maxn][maxn][maxn]; // distance from starting state int bfs() { queue<int> q; memset(d, -, sizeof(d)); q.push(ID(s[], s[], s[])); // starting node d[s[]][s[]][s[]] = ; while(!q.empty()) { int u = q.front(); q.pop(); )&)&0xff, c = u&0xff; ] && b == t[] && c == t[]) return d[a][b][c]; // solution found ; i < deg[a]; i++) { int a2 = G[a][i]; ; j < deg[b]; j++) { int b2 = G[b][j]; if(conflict(a, b, a2, b2)) continue; ; k < deg[c]; k++) { int c2 = G[c][k]; if(conflict(a, c, a2, c2)) continue; if(conflict(b, c, b2, c2)) continue; ) continue; d[a2][b2][c2] = d[a][b][c]+; q.push(ID(a2, b2, c2)); } } } } ; } int main() { int w, h, n; && n) { ][]; ; i < h; i++) fgets(maze[i], , stdin); // extract empty cells int cnt, x[maxn], y[maxn], id[maxs][maxs]; // cnt is the number of empty cells cnt = ; ; i < h; i++) ; j < w; j++) if(maze[i][j] != '#') { x[cnt] = i; y[cnt] = j; id[i][j] = cnt; if(islower(maze[i][j])) s[maze[i][j] - 'a'] = cnt; else if(isupper(maze[i][j])) t[maze[i][j] - 'A'] = cnt; cnt++; } // build a graph of empty cells ; i < cnt; i++) { deg[i] = ; ; dir < ; dir++) { int nx = x[i]+dx[dir], ny = y[i]+dy[dir]; // "Outermost cells of a map are walls" means we don't need to check out-of-bound if(maze[nx][ny] != '#') G[i][deg[i]++] = id[nx][ny]; } } // add fakes nodes so that in each case we have 3 ghosts. this makes the code shorter ) { deg[cnt] = ; G[cnt][] = cnt; s[] = t[] = cnt++; } ) { deg[cnt] = ; G[cnt][] = cnt; s[] = t[] = cnt++; } printf("%d\n", bfs()); } ; }
瞎:
①在进行搜索的时候,将可行区域抠出来接成一个邻接表,优化搜索效率
②双向搜索时候一层层判断 || 考虑如何判断状态已经一样?定义状态的时候需要考虑好。
③三个二维坐标点=>三个映射出来的整数=>三个整数通过二进制转化=>一个整数,就将6个坐标数表示的状态表示成了一个数
UVa 1601 || POJ 3523 The Morning after Halloween (BFS || 双向BFS && 降维 && 状压)的更多相关文章
- uva 1601 poj 3523 Morning after holloween 万圣节后的早晨 (经典搜索,双向bfs+预处理优化+状态压缩位运算)
这题数据大容易TLE 优化:预处理, 可以先枚举出5^3的状态然后判断合不合法,但是由于题目说了有很多墙壁,实际上没有那么多要转移的状态那么可以把底图抽出来,然后3个ghost在上面跑到时候就不必判断 ...
- UVA1601-The Morning after Halloween(双向BFS)
Problem UVA1601-The Morning after Halloween Accept: 289 Submit: 3136 Time Limit: 12000 mSec Problem ...
- 【UVa】1601 The Morning after Halloween(双向bfs)
题目 题目 分析 双向bfs,对着书打的,我还调了好久. 代码 #include<cstdio> #include<cstring> #include<c ...
- UVA - 1601 The Morning after Halloween (BFS/双向BFS/A*)
题目链接 挺有意思但是代码巨恶心的一道最短路搜索题. 因为图中的结点太多,应当首先考虑把隐式图转化成显式图,即对地图中可以相互连通的点之间连边,建立一个新图(由于每步不需要每个鬼都移动,所以每个点需要 ...
- UVA - 1601 The Morning after Halloween (双向BFS&单向BFS)
题目: w*h(w,h≤16)网格上有n(n≤3)个小写字母(代表鬼).要求把它们分别移动到对应的大写字母里.每步可以有多个鬼同时移动(均为往上下左右4个方向之一移动),但每步结束之后任何两个鬼不能占 ...
- POJ 2411 Mondriaan's Dream:网格密铺类 状压dp
题目链接:http://poj.org/problem?id=2411 题意: 给你一个n*m的网格 (1<=n,m<=11) ,往里面铺1*2或2*1的砖块,问你铺完这个网格有多少种不同 ...
- POJ 3126 Prime Path 解题报告(BFS & 双向BFS)
题目大意:给定一个4位素数,一个目标4位素数.每次变换一位,保证变换后依然是素数,求变换到目标素数的最小步数. 解题报告:直接用最短路. 枚举1000-10000所有素数,如果素数A交换一位可以得到素 ...
- <<操作,&0xff以及|的巧妙运用(以POJ3523---The Morning after Halloween(UVa 1601)为例)
<<表示左移,如a<<1表示将a的二进制左移一位,加一个0,&0xff表示取最后8个字节,如a&0xff表示取a表示的二进制中最后8个数字组成一个新的二进制数, ...
- 【POJ】3523 The Morning after Halloween
1. 题目描述$m \times n$的迷宫(最大为$16 \times 16$)包含最多3个点,这些点可以同时向相邻方向移动或保持停止,使用小写字母表示起始位置,使用大写字母表示中止位置.求最少经过 ...
随机推荐
- [转帖] Linux 下面 perl 命令的简介
https://www.jb51.net/article/123326.htm 感觉挺好的 改天需要仔细学习一下. 前言 本文主要给大家介绍了关于Perl单行命令的相关内容,分享出来供大家参考学习,下 ...
- [转帖]VMWare官网:无法关闭 ESXi 主机上的虚拟机 (1014165)
无法关闭 ESXi 主机上的虚拟机 (1014165) https://kb.vmware.com/s/article/1014165?lang=zh_CN Last Updated: 4/17/20 ...
- IIS服务搭建 试图加载格式不正确的程序
1.基础步骤 https://jingyan.baidu.com/article/fedf073770f23335ac8977b1.html 2.错误解决 试图加载格式不正确的程序 解决:在IIS ...
- 死磕并发之CountDownLatch解析
CountDownLatch解析 CountDownLatch是什么 CountDownLatch是基于AQS的阻塞工具,阻塞一个或者多个线程,直到所有的线程都执行完成. CountDownLatch ...
- [BJWC2008] Gate Of Babylon
题目链接 容斥+隔板法+Lucas定理 #include <bits/stdc++.h> using namespace std; const int N=1e5+10; int n,m, ...
- Node.js FS模块方法速查
1. File System 所有文件操作提供同步和异步的两种方式,本笔记只记录异步的API 异步方式其最后一个参数是回调函数.回调函数的第一个参数往往是错误对象,如果没有发生参数,那么第一个参数可能 ...
- 弹出ifream
top.$.jBox("iframe:"+'${ctx}/synopsis/hmlwxSynopsis/addItem', {title: "添加作品",w ...
- centos7配置fastdfs集群(5.09)
centos7配置fastdfs集群(5.09) 2017年03月10日 23:34:26 带鱼兄 阅读数 1564 版权声明:本文为博主原创文章,转载请注明出处. https://blog.c ...
- OpenCV处理文件、视频和摄像头
图像的本质(图像可以用数组来表示) import numpy as np import cv2 img = np.zeros((3, 3), dtype=np.uint8) print(img, im ...
- javascript 与node的 event-loop
参考:https://juejin.im/post/5c337ae06fb9a049bc4cd218 https://github.com/forthealllight/blog/issues/5 h ...