ACWing175. 電路維修 二解(bfs 與建圖求最短路)

題目傳送門:https://www.acwing.com/problem/content/177/
【題目大意】
達達是來自異世界的魔女,她在漫無目的地四處漂流的時候,遇到了善良的少女翰翰,從而被收留在地球上。翰翰的家裏有一輛飛行車。有一天飛行車的電路板突然出現了故障,導致無法啓動。電路板的整體結構是一個R行C列的網格(R,C≤500),如下圖所示。
在這裏插入圖片描述每個格點都是電線的接點,每個格子都包含一個電子元件。電子元件的主要部分是一個可旋轉的、連接一條對角線上的兩個接點的短電纜。在旋轉之後,它就可以連接另一條對角線的兩個接點。電路板左上角的接點接入直流電源,右下角的接點接入飛行車的發動裝置。達達發現因爲某些元件的方向不小心發生了改變,電路板可能處於斷路的狀態。她準備通過計算,旋轉最少數量的元件,使電源與發動裝置通過若干條短纜相連。不過,電路的規模實在是太大了,達達並不擅長編程,希望你能夠幫她解決這個問題。
【輸入格式】
輸入文件包含多組測試數據。
第一行包含一個整數T,表示測試數據的數目。
對於每組測試數據,第一行包含正整數R和C,表示電路板的行數和列數。
之後R行,每行C個字符,字符是"/“和”"中的一個,表示標準件的方向。
【輸出格式】
對於每組測試數據,在單獨的一行輸出一個正整數,表示所需的縮小旋轉次數。
如果無論怎樣都不能使得電源和發動機之間連通,輸出NO SOLUTION。
【數據範圍】
1≤R,C≤500,
1≤T≤5
【輸入樣例】:
1
3 5
\/\
\///
/\\
【輸出樣例】:
1
【樣例解釋】
樣例的輸入對應於題目描述中的情況。
只需要按照下面的方式旋轉標準件,就可以使得電源和發動機之間連通。
在這裏插入圖片描述
分析:
方法一:建圖+堆優化dijkstra求最短路
  題目中旋轉短電纜是需要花費1的代價的,旋轉後是另外兩個點的連接,那麼我們可以根據讀入的短電纜方向確定格點上邊權爲0的無向邊,那麼與其方向垂直的方向的格點可建立邊權爲1的無向邊。那麼會構成一個由點集0~(R+1)(C+1)-1組成的無向圖,求解0爲起點,(R+1) * (C+1)-1爲終點的最短路,邊權爲0或者1,因此可採用dijkstra求最短路,考慮到普通的dij時間複雜度爲O(N^2),其中N=501501(格點數),會超時,那麼考慮使用堆優化的dij,時間複雜度爲O(NlogN)可以通過。
注意建立圖時分析好格點與格子座標關係
在這裏插入圖片描述

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 500+6;
int r,c;
char s[MAXN][MAXN];
struct node {
	int u,v,w,next;
};
int d[MAXN*MAXN];
bool vis[MAXN*MAXN];
int cnt, head[MAXN*MAXN];
node edge[MAXN*MAXN*4];  //一個格子裏會有四條邊 
void init(){
	memset(head,0,sizeof(head));
	memset(edge,0,sizeof(edge));
	memset(s,0,sizeof(s));
	cnt = 0;
}
void read(){
	cin >> r >> c;
    for(int i= 0; i< r; i++) scanf("%s",s[i]);
} 
void addedge(int u,int v,int w){
	edge[++cnt]=(node){u,v,w,head[u]};
	head[u] = cnt;
}
void pre_edge(){
	int xc = c+1;
	for(int i = 0; i< r; i++){
		for(int j = 0; j< c; j++){
			if(s[i][j] == '/'){	 //將(i,j)座標的格子邊上四個點對應建立無向邊,構成一個圖。		
				addedge(xc*i+j, xc*(i+1)+j+1 , 1);
				addedge( xc*(i+1)+j+1 ,xc*i+j,  1);
				addedge(xc * i + j +1 ,xc*(i+1)+j , 0);
				addedge(xc *(i+1)+j ,xc * i + j +1, 0);
			}
			else{			
				addedge(xc*i+j, xc*(i+1)+j+1 , 0);
				addedge( xc*(i+1)+j+1 ,xc*i+j,  0);
				addedge(xc * i + j +1 ,xc*(i+1)+j , 1);
				addedge(xc *(i+1)+j ,xc * i + j +1 , 1);
			}
		}
	}
}
void dij(){  //堆優化的dijkstra求最短路 
	memset(d,0x3f,sizeof(d));
	memset(vis,0,sizeof(vis));
	priority_queue < pair<int,int> >q;
	int ed = (c+1)*r+c;
	d[0] = 0;
	q.push(make_pair(0,0));
	while(!q.empty()){
		int u = q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u] = true;
		for(int i = head[u];i; i = edge[i].next){
			int v = edge[i].v;
			int w = edge[i].w;
			if(d[u] + w < d[v]){
				d[v] = d[u] + w;
				q.push(make_pair(-1*d[v],v));
			}
		}		
	}
}
int main(){
    int t;
    cin >> t;
    while(t--){
    	init();
    	read();
    	pre_edge();
		dij();
    	int step = d[(c+1)*r+c] ;
    	if(step == 0x3f3f3f3f) printf("NO SOLUTION\n");
		else printf("%d\n",step); 
    }
    return 0;
}

方法二:ZYX寫的寬搜,在此記錄,用雙端隊列,如果這條分支時邊權爲0,就沿着該分支到達的新節點從隊頭入隊,如果這條分支是邊權爲1的邊,就從普通寬搜一樣從隊尾入,思路詳見代碼中註釋。

#include<bits/stdc++.h>
using namespace std;

const int Maxn=505;
const int gzx[4]={0,0,1,1},gzy[4]={0,1,1,0};
const int gz[4]={0,1,0,1},dx[4]={-1,-1,1,1},dy[4]={-1,1,1,-1};

int r,c;
int Map[Maxn][Maxn];//每一個格子的電纜 0:\ 1:/
int minstep[Maxn][Maxn];
bool vis[Maxn][Maxn];
//起點座標爲(0,0) 終點座標爲(r,c)

struct node{
    int x,y,cnt;
};

bool check(int x,int y){
    if(x<0||y<0||x>r||y>c)return false;
    return true;
}

void work(){
    deque<node> q;
    memset(vis,0,sizeof(vis));
    memset(minstep,0x3f,sizeof(minstep));
    q.push_front((node){0,0,0});
    while(!q.empty()){
        node temp=q.front();
        q.pop_front();
        int x=temp.x,y=temp.y,cnt=temp.cnt;
        if(vis[x][y])continue;
        vis[x][y]=true;
        if(x==r && y==c){printf("%d\n",cnt);return;}
        for(int i=0;i<4;i++){
            int gx=x+gzx[i],gy=y+gzy[i];
            if(gx>0 && gy>0 && gx<=r && gy<=c){
                int nx=x+dx[i],ny=y+dy[i];
                if(check(nx,ny)){
                	if(minstep[nx][ny]==0x3f3f3f3f){
                		if(Map[gx][gy]!=gz[i]){q.push_back((node){nx,ny,cnt+1});minstep[nx][ny]=cnt+1;} 
                   		else{q.push_front((node){nx,ny,cnt});minstep[nx][ny]=cnt;}
                	}
                    else if(Map[gx][gy]==gz[i] && cnt<minstep[nx][ny]){
                    	q.push_front((node){nx,ny,cnt});
                    }
                }
            }
        }
    }  
    printf("NO SOLUTION\n");
}

int main(){
    //可以理解成從起點走向不同的格點 中途可以更改道路,最後走到終點
    //那麼過程中就要記錄當前更改道路的次數,和寬搜一樣,保證了到達一個格點時,一定是用了最少的更改道路
    //然後還要注意一個細節:從一個點向其他點行走時不一定要更改道路,也就是說更改道路次數不一定都是遞增的,所以要用雙端隊列維護隊列單調性
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&r,&c);
        for(int i=1;i<=r;i++){
            for(int j=1;j<=c;j++){
                char c=getchar();
                while(c!='\\'&&c!='/')c=getchar();
                if(c=='\\')Map[i][j]=0;
                else Map[i][j]=1;
                //將字符轉化爲數字
            }
        }    
        work();
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章