题解 P1825 【[USACO11OPEN]玉米田迷宫Corn Maze】

玉米田迷宫题解

一.背景

​ 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<bits/stdc++.h>
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<pair<int,pair<int,int> > >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;
}
posted @ 2019-02-19 21:54  ThinkofBlank  阅读(251)  评论(0编辑  收藏  举报