题目链接

挺有意思但是代码巨恶心的一道最短路搜索题。

因为图中的结点太多,应当首先考虑把隐式图转化成显式图,即对地图中可以相互连通的点之间连边,建立一个新图(由于每步不需要每个鬼都移动,所以每个点需要向自己也连一条边)。设d[i][j][k]为走到“A在结点i,B在结点j,C在结点k”的状态需要多少步,直接bfs即可。

注意由于鬼的个数不确定,为了减少特判,需要留出三个虚节点,把多出来的鬼的起点和终点都设到同一个虚节点上。

(代码刚写完后发现样例的答案比正确的少了2,检查了好久才发现自己建图的时候tot多加了1...)

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=+;
struct D {int a[];};
struct E {int v,nxt;} e[];
int rt[N][N],d[][][],n,m,k,tot,ne,hd[],bg[],ed[];
char s[N][N];
void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
bool ok(int* u,int* v) {
if(v[]==v[]||v[]==v[]||v[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
return ;
} int bfs() {
int u[],v[];
queue<D> q;
q.push({bg[],bg[],bg[]}),d[bg[]][bg[]][bg[]]=;
while(!q.empty()) {
memcpy(u,q.front().a,sizeof u),q.pop();
if(u[]==ed[]&&u[]==ed[]&&u[]==ed[])return d[u[]][u[]][u[]];
for(int i=hd[u[]]; ~i; i=e[i].nxt)
for(int j=hd[u[]]; ~j; j=e[j].nxt)
for(int k=hd[u[]]; ~k; k=e[k].nxt) {
v[]=e[i].v,v[]=e[j].v,v[]=e[k].v;
if(ok(u,v)&&!~d[v[]][v[]][v[]]) {
d[v[]][v[]][v[]]=d[u[]][u[]][u[]]+;
q.push({v[],v[],v[]});
}
}
}
return -;
} int main() {
while(scanf("%d%d%d",&m,&n,&k)&&n) {
scanf(" ");
memset(hd,-,sizeof hd),ne=,tot=;
for(int i=; i<; ++i)bg[i]=ed[i]=i;
addedge(,),addedge(,),addedge(,);
for(int i=; i<n; ++i)gets(s[i]);
for(int i=; i<n-; ++i)for(int j=; j<m-; ++j)if(s[i][j]!='#') {
rt[i][j]=tot++;
addedge(rt[i][j],rt[i][j]);
if(s[i-][j]!='#') {
addedge(rt[i][j],rt[i-][j]);
addedge(rt[i-][j],rt[i][j]);
}
if(s[i][j-]!='#') {
addedge(rt[i][j],rt[i][j-]);
addedge(rt[i][j-],rt[i][j]);
}
if(isupper(s[i][j]))bg[s[i][j]-'A']=rt[i][j];
else if(islower(s[i][j]))ed[s[i][j]-'a']=rt[i][j];
}
for(int i=; i<tot; ++i)for(int j=; j<tot; ++j)for(int k=; k<tot; ++k)d[i][j][k]=-;
printf("%d\n",bfs());
}
return ;
}

bfs

这个代码跑了1000+ms,我们可以继续优化。

优化一:由于把一步移动撤回的规则和正向移动的规则是一样的,因此可以把bfs改成双向的,即6个鬼同时从起点和终点出发直到相遇,这样可以降低bfs树的深度,少扩展一些结点。方法是将d数组多开一维,代表每个状态是正向转移来的还是反向转移来的。如果一个状态的反状态(对应鬼的位置均相等,bfs方向相反),则两状态的d值相加即为最短距离。

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=+;
struct D {int f,a[];};
struct E {int v,nxt;} e[];
int rt[N][N],d[][][][],n,m,k,tot,ne,hd[],bg[],ed[];
char s[N][N];
void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
bool ok(int* u,int* v) {
if(v[]==v[]||v[]==v[]||v[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
return ;
} int bfs() {
int u[],v[],f;
queue<D> q;
q.push({,bg[],bg[],bg[]}),d[][bg[]][bg[]][bg[]]=;
q.push({,ed[],ed[],ed[]}),d[][ed[]][ed[]][ed[]]=;
while(!q.empty()) {
memcpy(u,q.front().a,sizeof u),f=q.front().f,q.pop();
if(~d[f^][u[]][u[]][u[]])return d[f][u[]][u[]][u[]]+d[f^][u[]][u[]][u[]];
for(int i=hd[u[]]; ~i; i=e[i].nxt)
for(int j=hd[u[]]; ~j; j=e[j].nxt)
for(int k=hd[u[]]; ~k; k=e[k].nxt) {
v[]=e[i].v,v[]=e[j].v,v[]=e[k].v;
if(ok(u,v)&&!~d[f][v[]][v[]][v[]]) {
d[f][v[]][v[]][v[]]=d[f][u[]][u[]][u[]]+;
q.push({f,v[],v[],v[]});
}
}
}
return -;
} int main() {
while(scanf("%d%d%d",&m,&n,&k)&&n) {
scanf(" ");
memset(hd,-,sizeof hd),ne=,tot=;
for(int i=; i<; ++i)bg[i]=ed[i]=i;
addedge(,),addedge(,),addedge(,);
for(int i=; i<n; ++i)gets(s[i]);
for(int i=; i<n-; ++i)for(int j=; j<m-; ++j)if(s[i][j]!='#') {
rt[i][j]=tot++;
addedge(rt[i][j],rt[i][j]);
if(s[i-][j]!='#') {
addedge(rt[i][j],rt[i-][j]);
addedge(rt[i-][j],rt[i][j]);
}
if(s[i][j-]!='#') {
addedge(rt[i][j],rt[i][j-]);
addedge(rt[i][j-],rt[i][j]);
}
if(isupper(s[i][j]))bg[s[i][j]-'A']=rt[i][j];
else if(islower(s[i][j]))ed[s[i][j]-'a']=rt[i][j];
}
for(int i=; i<tot; ++i)for(int j=; j<tot; ++j)for(int k=; k<tot; ++k)d[][i][j][k]=d[][i][j][k]=-;
printf("%d\n",bfs());
}
return ;
}

bfs(双向)

跑了600+ms,感觉也没快多少~~

优化二:可以考虑用A*算法,新开一个h数组记录每个节点分别到a,b,c结点的最短距离(可用bfs预处理),则当前状态(i,j,k)到(a,b,c)的最短距离不超过f[i][j][k]=d[i][j][k]+max(h[a][i],h[b][j],h[c][k]),选择把原来的队列换成优先队列,每次取出f值最小的结点进行扩展即可。

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=+;
struct E {int v,nxt;} e[];
int rt[N][N],d[][][],h[][],n,m,k,tot,ne,hd[],bg[],ed[];
char s[N][N];
struct D {
int a[];
bool operator<(const D& b)const {
return d[a[]][a[]][a[]]+max(h[][a[]],max(h[][a[]],h[][a[]]))
>d[b.a[]][b.a[]][b.a[]]+max(h[][b.a[]],max(h[][b.a[]],h[][b.a[]]));
}
};
void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
bool ok(int* u,int* v) {
if(v[]==v[]||v[]==v[]||v[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
if(u[]==v[]&&u[]==v[])return ;
return ;
} void bfs(int S,int* d) {
int u,v;
queue<int> q;
for(int i=; i<tot; ++i)d[i]=-;
q.push(S),d[S]=;
while(!q.empty()) {
u=q.front(),q.pop();
for(int i=hd[u]; ~i; i=e[i].nxt) {
v=e[i].v;
if(!~d[v])d[v]=d[u]+,q.push(v);
}
}
} int Astar() {
int u[],v[];
priority_queue<D> q;
d[bg[]][bg[]][bg[]]=,q.push({bg[],bg[],bg[]});
while(!q.empty()) {
memcpy(u,q.top().a,sizeof u),q.pop();
if(u[]==ed[]&&u[]==ed[]&&u[]==ed[])return d[u[]][u[]][u[]];
for(int i=hd[u[]]; ~i; i=e[i].nxt)
for(int j=hd[u[]]; ~j; j=e[j].nxt)
for(int k=hd[u[]]; ~k; k=e[k].nxt) {
v[]=e[i].v,v[]=e[j].v,v[]=e[k].v;
if(ok(u,v)&&!~d[v[]][v[]][v[]]) {
d[v[]][v[]][v[]]=d[u[]][u[]][u[]]+;
q.push({v[],v[],v[]});
}
}
}
return -;
} int main() {
while(scanf("%d%d%d",&m,&n,&k)&&n) {
scanf(" ");
memset(hd,-,sizeof hd),ne=,tot=;
for(int i=; i<; ++i)bg[i]=ed[i]=i;
addedge(,),addedge(,),addedge(,);
for(int i=; i<n; ++i)gets(s[i]);
for(int i=; i<n-; ++i)for(int j=; j<m-; ++j)if(s[i][j]!='#') {
rt[i][j]=tot++;
addedge(rt[i][j],rt[i][j]);
if(s[i-][j]!='#') {
addedge(rt[i][j],rt[i-][j]);
addedge(rt[i-][j],rt[i][j]);
}
if(s[i][j-]!='#') {
addedge(rt[i][j],rt[i][j-]);
addedge(rt[i][j-],rt[i][j]);
}
if(isupper(s[i][j]))bg[s[i][j]-'A']=rt[i][j];
else if(islower(s[i][j]))ed[s[i][j]-'a']=rt[i][j];
}
for(int i=; i<tot; ++i)for(int j=; j<tot; ++j)for(int k=; k<tot; ++k)d[i][j][k]=-;
for(int i=; i<; ++i)bfs(ed[i],h[i]);
printf("%d\n",Astar());
}
return ;
}

A*

我用A*算法跑样例的速度明显快了好几个档次,但提交上去却依旧跑了400+ms,看来A*终究逃不过被卡的命运~~

UVA - 1601 The Morning after Halloween (BFS/双向BFS/A*)的更多相关文章

  1. UVA - 1601 The Morning after Halloween (双向BFS&单向BFS)

    题目: w*h(w,h≤16)网格上有n(n≤3)个小写字母(代表鬼).要求把它们分别移动到对应的大写字母里.每步可以有多个鬼同时移动(均为往上下左右4个方向之一移动),但每步结束之后任何两个鬼不能占 ...

  2. 【UVa】1601 The Morning after Halloween(双向bfs)

    题目 题目     分析 双向bfs,对着书打的,我还调了好久.     代码 #include<cstdio> #include<cstring> #include<c ...

  3. UVa 1601 || POJ 3523 The Morning after Halloween (BFS || 双向BFS && 降维 && 状压)

    题意 :w*h(w,h≤16)网格上有n(n≤3)个小写字母(代表鬼).要求把它们分别移动到对应的大写字母里.每步可以有多个鬼同时移动(均为往上下左右4个方向之一移动),但每步结束之后任何两个鬼不能占 ...

  4. UVA1601-The Morning after Halloween(双向BFS)

    Problem UVA1601-The Morning after Halloween Accept: 289 Submit: 3136 Time Limit: 12000 mSec  Problem ...

  5. POJ1915Knight Moves(单向BFS + 双向BFS)

    题目链接 单向bfs就是水题 #include <iostream> #include <cstring> #include <cstdio> #include & ...

  6. POJ 3126 Prime Path 解题报告(BFS & 双向BFS)

    题目大意:给定一个4位素数,一个目标4位素数.每次变换一位,保证变换后依然是素数,求变换到目标素数的最小步数. 解题报告:直接用最短路. 枚举1000-10000所有素数,如果素数A交换一位可以得到素 ...

  7. UVA 1601 The Morning after Halloween

    题意: 给出一个最大为16×16的迷宫图和至多3个ghost的起始位置和目标位置,求最少经过几轮移动可以使三个ghost都到达目标位置.每轮移动中,每个ghost可以走一步,也可以原地不动,需要注意的 ...

  8. POJ1915 BFS&双向BFS

    俩月前写的普通BFS #include <cstdio> #include <iostream> #include <cstring> #include <q ...

  9. bfs(双向bfs加三维数组)

    http://acm.hdu.edu.cn/showproblem.php?pid=2612 Find a way Time Limit: 3000/1000 MS (Java/Others)     ...

随机推荐

  1. jQuery:自学笔记(4)——事件与事件对象

    jQuery:自学笔记(4)——事件与事件对象 jQuery中的事件 什么是事件 所谓事件,就是被对象识别的操作,即操作对象队环境变化的感知和反应,例如单击按钮或者敲击键盘上的按键. 所谓事件流,是指 ...

  2. jdbc驱动jar导入eclipse

    在使用JDBC编程时需要连接数据库,导入JAR包是必须的,导入其它的jar包方法同样如此,导入的方法是 打开eclipse 1.右击要导入jar包的项目,点properties 2.左边选择java ...

  3. iOS系统自带分享功能

    很多APP中都带有社交分享功能,通过用户的分享,让更多地人去了解和使用这个APP,目前社交分享是移动互联网应用程序推广的最重要手段之一,国内较或的分享平台有微信,IOS6后苹果集成的新浪微博,还有IO ...

  4. Linux Shell基础 多个命令中的分号(;)、与(&&) 、 或(||)

    概述 在 Bash 中,如果需要让多条命令按顺序执行,则有这样方法,如表 1 所示. 多命令执行符 格 式 作 用 : 命令1 ; 命令2 多条命令顺序执行,命令之间没有任何逻辑关系 &&am ...

  5. pppoe白皮书

    转:https://blog.csdn.net/achejq/article/details/19478811 PPPoE技术白皮书 关键词:PPP,Ethernet,PPPoE 摘    要:PPP ...

  6. git 使用教程 --基础一

    第一步:下载git   https://git-scm.com/ 第二步: 切到需要保存的文件夹下,执行: bogon:VBV mona$ git init #初始化,表示即将对当前文件夹进行版本控制 ...

  7. @MarkFan 口语练习录音 20140401

    Hi,everybody 对于未来,我只梦想最好的情况, 并定下最踏实的计划,而绝不花时间在无谓的担心上, 因为我知道,只要把我对自己的承诺付诸实践, 我的未来将不会只是一个梦…… 这是引用考拉小巫的 ...

  8. C++使用命名空间中成员的三种方式

    通过简单的代码来介绍使用命名空间中成员的三种方式(我们最常用到的命名空间是是标准库std,下面的命名空间都以std为例): 使用作用域符:: #include<iostream> int ...

  9. springmvc中url-url-pattern /和/*的区别

    在使用springmvc时,都会在web.xml中配置一个dispatchservlet,如下: <listener> <listener-class> org.springf ...

  10. 1028: [JSOI2007]麻将

    1028: [JSOI2007]麻将 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 2638  Solved: 1168[Submit][Status] ...