zoukankan      html  css  js  c++  java
  • 题解 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;
    }
    
  • 相关阅读:
    乘法逆元
    P1082 同余方程
    数论编程
    倍增LCA模板
    快速幂模板Super
    黑白染色的模板
    暑假提高组集训Day1 T2
    暑假提高组集训Day1 T1
    7月18日刷题记录 二分答案跳石头游戏Getting
    hdu4738(割桥)
  • 原文地址:https://www.cnblogs.com/ThinkofBlank/p/10403608.html
Copyright © 2011-2022 走看看