玉米田迷宫题解
一.背景
x年x月x日,竞赛老师拿此题问我,然后我玄学过了,于是特来写此题。(谁说dijkstra不能过的??)
二.分析
本题,我们先不考虑有传送阵的情况,发现,其实就是一个最短路(bfs)的模板题,随便弄下就能过,不过,这里多了个传送阵,于是我们就要考虑下怎么做题了。。。
Method 1
考虑bfs。
通过观察发现,本题的边的权已经不止是我们平常所做的只有1的边权了,还多了传送阵之间的0边权,所以,本题存在0、1两种边权,怎么搞呢?
有个东西叫做01bfs,专门处理这种情况,我们把队列修改为双端队列,一个点,如果是被1边权扩展出来的,我们把它插入队尾,反之,如果它是由0边权扩展出来的,我们把它插入队首,其余的跟普通bfs一样。
至于为什么是对的,机智的你用bfs是一层一层的扩展的原理来分析便显然易知了
那么如何维护题目中的到达传送阵必须传送呢?很简单,我们在队列里面加维护一个bool值flag,flag=0表示我们准备进入传送阵,flag=1表示我们刚从传送阵出来,即可。(记得flag类讨论)
然后。。。就没然后了。。。
Method 2
同样考虑bfs。
现在,令我们恼火的有两个东西:
1.0边权(最短路不需要考虑)
2.到达传送阵必须启动的条件
那么我们怎么做可以忽视这两点从而使得问题转化为一个普通的bfs/最短路呢?
我们这样考虑:既然,我们到达传送阵后必须启用,那么,为什么我们不直接跳过传送阵呢?于是,对于一个传送阵,我们把它和它所对应的传送阵的相邻格子(即一步可以到达的格子)直接连边,这是,我们发现,我们所有的边权都是1了,一遍bfs/最短路即可!
至于为什么是对的呢?我们发现,我们唯一没有求出起点到最短路的距离的点就是传送阵了,而传送阵一定不是终点(显然),所以,我们可以求得答案!
Method 3
考虑最短路。
我们考虑网络流的"拆点"思想,我们将每个点分为0、1两个状态。
0表示流入,1表示流出,同一个点0号到1号之间的边权为0,不与其他点连边,1号则到相邻的点的0号状态。
而对于传送阵,0号只与对应的传送阵的1号状态连边(边权0),1号状态则与四周点连边(边权1),即可,然后,跑最短路(spfa,dijkstra)即可。
这样便能符合条件,然后乱跑就过了
考虑下优化:其他点我们完全没有必要拆成两个点,我们其他点只管0号状态,而对于传送阵,我们再单独维护0,1两个状态,这时,像method1一样,加维护一个bool表示状态再分类处理即可!
稍微提下:dijkstra只是不能处理负环,正权环还是可以处理的 = =
三.代码
由于Method 1,2太"妙"了(其实是懒得打了),我就不放代码了,放个Method 3的代码:
//#pragma GCC optimize()//手动Ox优化
#include
using namespace std;
const int N=601,M=180000;
char a[N][N];
int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0};
struct node{
int v,w,nex;
}t[M<<1];
struct strom{//传送阵
int x[3],y[3],e;
}p[26];
bool ys[M];
int las[M],len;
int dis[M][2];
bool vis[M][2];
priority_queue > >s;
inline void dijkstra(int X){//dijkstra
memset(dis,0x3f3f,sizeof(dis));
dis[X][0]=0;
s.push(make_pair(0,make_pair(X,0)));
while(!s.empty()){
int x=s.top().second.first;
int y=s.top().second.second;
s.pop();
if(vis[x][y]){
continue;
}
vis[x][y]=1;
if(!y){//分类讨论
for(int i=las[x];i;i=t[i].nex){
int v=t[i].v,w=t[i].w,T=ys[v];
if(dis[v][T]>dis[x][y]+w){
dis[v][T]=dis[x][y]+w;
s.push(make_pair(-dis[v][T],make_pair(v,T)));
}
}
}else{
for(int i=las[x];i;i=t[i].nex){
if(t[i].w){
continue;
}
int v=t[i].v;
if(dis[v][0]>dis[x][1]){
dis[v][0]=dis[x][1];
s.push(make_pair(-dis[v][0],make_pair(v,0)));
}
}
}
}
}
inline void add(int u,int v,int w=0){
len++;
t[len].v=v,t[len].w=w;
t[len].nex=las[u],las[u]=len;
}
int main(){
// freopen("233.in","r",stdin);
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%s",a[i]);
}
int S,E;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(a[i][j-1]=='#'){
continue;
}
if(a[i][j-1]=='@'){
S=(i-1)*m+j;
}
if(a[i][j-1]=='='){
E=(i-1)*m+j;
}
if(a[i][j-1]>='A'&&a[i][j-1]<='Z'){//建立传送阵
ys[(i-1)*m+j]=1;
int v=a[i][j-1]-'A';
int now=++p[v].e;
p[v].x[now]=i,p[v].y[now]=j;
}
for(int k=0;k<4;++k){
int xx=i+dx[k],yy=j+dy[k];
if(xx>0&&xx<=n&&yy>0&&yy<=m&&a[xx][yy-1]!='#'){//四周连边
add((i-1)*m+j,(xx-1)*m+yy,1);
}
}
}
}
for(int i=0;i<26;++i){
if(p[i].e){
add((p[i].x[1]-1)*m+p[i].y[1],(p[i].x[2]-1)*m+p[i].y[2]);
add((p[i].x[2]-1)*m+p[i].y[2],(p[i].x[1]-1)*m+p[i].y[1]);
}//传送阵连边
}
dijkstra(S);
printf("%d",dis[E][0]);
return 0;
}