题目大意:
对于一个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 }
今天就到这里啦~再见呦(●‘?‘●)~~撒花撒花*★,°*:.☆\( ̄▽ ̄)/$:*.°★*
时间: 2024-10-06 19:54:54