題目大意:
對於一個n個房間m條路徑的迷宮(Labyrinth)(2<=n<=100000, 1<=m<=200000),每條路徑上都塗有顏色,顏色取值范圍為1<=c<=10^9。求從節點1到節點n的一條路徑,使得經過的邊盡量少,在這樣的前提下,如果有多條路徑邊數均為最小,則顏色的字典序最小的路徑獲勝。一條路徑可能連接兩個相同的房間,一對房間之間可能有多條路徑。輸入保證可以從節點1到達節點n。
更多細節可以參考原題:UVa1599
分析:
- 從題目中我們可以看出,題目中的無向圖是可以出現自環和重邊的,自環我們可以在輸入的時候檢查並排除,但是重邊我們需要保留,並從中選擇顏色最小的邊。
- 題目的數據量很大,不可能采用鄰接矩陣存儲圖,因此應采用鄰接表,且鄰接表便於進行bfs
- 路徑的顏色不代表路徑的權重,本題中路徑是無權的
思路:
從終點開始倒着bfs一次,得到每個點到終點的距離,然后從起點開始,按照每次距離減1的方法尋找接下來的點的編號。按照顏色最小的走,如果有多個顏色最小,則都拉入隊列中,將最小的顏色記錄在res數組中。
其中,index=d[0]-d[u]就得到了當前u節點對應的距離,也就是步驟數。
細節:
- 已經進入隊列的節點不能重復入隊,否則復雜度太高,會tle(重復入隊的復雜度至少是O(n^2),在n=100000的情況下直接tle)
- 第一次bfs和第二次bfs的終止時機不同,第一次找到起點就終止,第二次則是從隊列中取出節點時才能終止,為的是遍歷完所有導向終點且路徑長度一致的邊,只有這樣才能結果正確
- d數組記錄每個節點到終點n的距離,不能用0進行初始化,而終點處的初始化必須是0
- d數組不能不初始化,否則對於多輸入題目,前面的輸入可能影響后面的輸出
代碼實現如下:
1 #include<cstdio> 2 #include<vector> 3 #include<queue> 4 #include<cstring> 5 using namespace std; //min()函數 6 #define max 100000 7 #define inf 0x7fffffff 8 typedef struct ver{ 9 int num, color;//邊的另一端的節點編號 和 顏色 10 ver(int n,int c):num(n),color(c){} 11 }Ver; 12 int n,m,a,b,c; 13 int d[max],res[max];//d記錄每個點到終點的最短距離 res記錄最短路的顏色 14 bool vis[max],inqueue[max];//vis每個節點是否被訪問過 inqueue標記節點是否加入了隊列,防止重復加入 15 vector<Ver> edge[max];//鄰接表記錄圖 16 void bfs(int start,int end){ 17 memset(inqueue,0,n); 18 memset(vis,0,n); 19 int u,v,c; 20 queue<int> q; 21 q.push(start); 22 if(start==0){//用於正向bfs 23 memset(res,0,sizeof(int)*n); 24 while(!q.empty()){ 25 u=q.front();q.pop();vis[u]=1; 26 if(u==n-1)return; 27 int minc=inf,len=edge[u].size(); 28 for(int i=0;i<len;i++)if(!vis[v=edge[u][i].num] && d[u]-1==d[v])minc=min(edge[u][i].color,minc);//獲取所有路徑中最小的顏色 29 for(int i=0;i<len;i++)if(!vis[v=edge[u][i].num] && d[u]-1==d[v] && edge[u][i].color==minc && !inqueue[v])q.push(v),inqueue[v]=1; //若有多組顏色相同,且未入隊,則將其入隊 30 int index=d[0]-d[u];//獲得當前步數對應的下標 31 if(res[index]==0)res[index]=minc; 32 else res[index]=min(res[index],minc);//獲取最小顏色 33 } 34 }//用於反向bfs 構建層次圖,找最短路 35 else while(!q.empty()){ 36 u=q.front();q.pop();vis[u]=1; 37 for(int i=0,len=edge[u].size();i<len;i++)if(!vis[v=edge[u][i].num] && !inqueue[v]){ 38 d[v]=d[u]+1; //一定是頭一次入隊,這通過inqueue保證 39 if(v==0)return; //找到起點,退出 40 q.push(v);//如果不是起點,就把這個點入隊 41 inqueue[v]=1;//入隊標記 42 } 43 } 44 } 45 int main(){ 46 while(scanf("%d%d",&n,&m)==2){ 47 for(int i=0;i<n;i++)edge[i].clear(); 48 memset(d,-1,sizeof(int)*n);d[n-1]=0;//注意初始化的細節 49 while(m--){ 50 scanf("%d%d%d",&a,&b,&c); 51 if(a!=b){ //排除自環 52 edge[a-1].push_back(ver(b-1,c)); 53 edge[b-1].push_back(ver(a-1,c)); 54 } 55 } 56 bfs(n-1,0);//反向bfs 57 bfs(0,n-1);//正向bfs 58 printf("%d\n%d",d[0],res[0]); 59 for(int i=1;i<d[0];i++)printf(" %d",res[i]); 60 printf("\n"); 61 } 62 }
收獲:
這是第一次學習bfs遍歷復雜圖,對於重邊和自環的處理也終於有了一點經驗,積累了自己的bfs最短路的模板
另外,UVa上的數據並不是完全可靠,對於用0初始化數組d的行為,可以用這組數據測試出wa的結果:
Input:
4 3
1 2 1
1 3 1
1 4 7
Output:
1
7
但是我實驗發現,如果用0對數組d進行初始化,在UVa上仍能AC,不過我已經給UVa寫信報告了這個bug,不知道他們會不會做修正。
不論如何,這道題還是收獲很大滴~接下來是
反向bfs尋找最短路的模板
注意:d數組初始化應該用-1,並將d[n-1]=0,否則就會出現上述UVa的bug
這份代碼假設在輸入的時候重邊已經被排除,否則這份代碼還需要加入u!=v的判斷
代碼如下:
1 void rbfs(){ 2 memset(inqueue,0,sizeof(inqueue)); 3 memset(vis,0,sizeof(vis)); 4 queue<int> q;q.push(n-1); 5 while(!q.empty()){ 6 u=q.front();q.pop();vis[u]=1; 7 for(int i=0,len=edge[u].size();i<len;i++)if(!vis[v=edge[u][i].num] && !inqueue[v]){ //inqueue是為了防止重復入隊造成復雜度過高,以本題為例,如果允許重復入隊會直接超時 8 d[v]=d[u]+1; 9 if(v==0)return; //找到起點,退出 10 q.push(v);//如果不是起點,就把這個點入隊 11 inqueue[v]=1;//入隊標記 12 } 13 } 14 }
今天就到這里啦~再見呦(●'◡'●)~~撒花撒花*★,°*:.☆\( ̄▽ ̄)/$:*.°★*