紫书刷题进行中,题解系列【GitHub|CSDN】
例题6-20 UVA1599 Ideal Path(67行AC代码)
题目大意
给定一个n个顶点,m条边的无向图,每条边有颜色值(长度均为1),求出起点到终点的最短路径(颜色值表示),若存在多解,则输出路径中颜色值字典序最小者。(注意重边,自环)
思路分析
本题有两个条件:最短+字典序最小
最短路用bfs可轻易解决,但字典序稍微棘手些,不过我们可以借用控制变量法,先从终点逆向bfs求出到每一点的距离,此时再从起点bfs,若下一个点v的距离与当前点u距离相差1,说明走点v一定可以到达终点(并且最短),这时候就可以从所有候选点钟选出颜色值最小的加入队列。
本质就是做两次bfs,从终点开始的bfs保证了最短可达的条件,从起点开始保证字典序最小条件
算法设计
本题数据量很大,只能采用邻接表存储图,为了进一步提高效率,将边统一缓存在edge
数组中,数组下标表示边的编号,在邻接表中只存储边的编号即可,提高索引访问速度,避免超时
struct Edge { // 边
int u, v, c;
Edge(int _u, int _v, int _c) : u(_u), v(_v), c(_c) {} // 默认构造函数
};
vector<Edge> edge; // 边的缓存,数组下标作为编号
vector<int> G[maxn]; // 邻接表存图,边用编号表示,提高效率;
同时,养成bfs的好习惯,在顶点入队时立刻标记为已访问,避免同一顶点反复入队,照成资源浪费,况且本题还存在重边,更需注意该细节处理
注意点
- 输出路径时最后一个元素后无空格
- 注意顶点从0还是1开始
- 变量使用前注意初始化,其中,能用普通数组的尽量不用stl容器,能用memset尽量不用fill,原始的东西效率较高,但memset只能初始化为0或-1,因为他是对一段内存的位赋值
AC代码(C++11,双向bfs,边缓存优化)
#include<bits/stdc++.h>
using namespace std;
const int maxn=100001; // 最大顶点数
struct Edge { // 边
int u, v, c;
Edge(int _u, int _v, int _c) : u(_u), v(_v), c(_c) {} // 默认构造函数
};
vector<Edge> edge; // 边的缓存,数组下标作为编号
vector<int> G[maxn]; // 邻接表存图,边用编号表示,提高效率;
int n, m, a, b, c;
int d[maxn], vis[maxn]; // d:终点到各个点的距离(层次);vis:访问数组
void revBfs() { // 从终点开始遍历,计算到每个点的距离
memset(d, 0, sizeof(d)); memset(vis, 0, sizeof(vis)); // 初始化
queue<int> q;
q.push(n-1); vis[n-1] = 1; // 终点入队
while (!q.empty()) {
int u = q.front(); q.pop(); // 顶点出队
for (int e : G[u]) { // 每条邻边
int v = (u == edge[e].u) ? edge[e].v : edge[e].u;
if (vis[v] == 0) { // 未访问
d[v] = d[u] + 1; // 距离/层次更新
q.push(v);
vis[v] = 1; // 标记访问
}
}
}
}
void bfs() { // 从起点开始bfs,记录字典序最小的路径
printf("%d\n", d[0]); // 最短距离
memset(vis, 0, sizeof(vis)); // 初始化
vector<int> next{0}, ans;
for (int i=0; i < d[0]; i ++) { // 分层遍历
int minColor=0x3fffffff; // 最小颜色值
for (int u : next) // 该层顶点(找出本层到下一层的最小颜色值)
for (int e : G[u]) { // 所有边
int v = (u == edge[e].u) ? edge[e].v : edge[e].u; // 确定下一个顶点
if (d[u] == d[v]+1 && edge[e].c < minColor) minColor = edge[e].c; // 找出可达终点的最小颜色边
}
ans.push_back(minColor); // 存储最小颜色
vector<int> tnext; // 临时存储
for (int u : next) // 将本层到下一层颜色值满足最小的点加入下一轮next
for (int e : G[u]) {
int v = (u == edge[e].u) ? edge[e].v : edge[e].u;
if (d[u] == d[v]+1 && vis[v] == 0 && edge[e].c == minColor) {
tnext.push_back(v);
vis[v] = 1;
}
}
next = tnext; // 更新next数组
}
for (int i=0; i < ans.size(); i ++) printf("%d%s", ans[i], i == ans.size()-1 ? "\n" : " ");
}
int main() {
while (scanf("%d %d", &n, &m) == 2) {
edge.clear(); fill(G, G+maxn, vector<int>{}); // 初始化
while (m --) {
scanf("%d %d %d", &a, &b, &c);
if (a == b) continue; // 处理自环
G[a-1].push_back(edge.size()); // 无向图,从0开始存储
G[b-1].push_back(edge.size());
edge.push_back({a-1,b-1,c}); // 边缓存
}
revBfs();
bfs();
}
return 0;
}