一、题意
给一个n个点m条边(2<=n<=100000, 1<=m<=200000)的无向图,每条边都涂有一种颜色。求从结点1到结点n的一条最短路径。在此前提下,使得经过边的颜色序列的字典序最小。打印出路径长度和颜色序列。
二、解析
最短路问题的考虑bfs。但是这道题目要打印出最短路径,如果用平常的父节点p数组的方法,没有办法保证按照颜色字典序。因此要考虑其他方法。
这里的方法是使用两次bfs。
第一次是从终点开始的逆向bfs,可以得到所有顶点到终点的距离数组rev_d[maxn];然后再进行正向bfs,正向bfs时就要沿着rev_d逐一递减的方向遍历,然后在每一步选择颜色最小的方向(可能有多个)。
三、代码
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 100000 + 5, INF = 1 << 30;
struct edge {
int to, c;
edge(int to, int c) : to(to), c(c) {}
};
vector<edge> G[maxn];
int n, m, rev_d[maxn], vis[maxn];
void rev_bfs() {
fill(vis, vis + n + 1, 0);
fill(rev_d, rev_d + n + 1, 0);
queue<int> que;
que.push(n), vis[n] = 1, rev_d[n] = 0;
while(!que.empty()) {
int u = que.front();
que.pop();
for(const edge& e : G[u]) if(!vis[e.to]) {
que.push(e.to), vis[e.to] = 1, rev_d[e.to] = rev_d[u] + 1;
}
}
}
void bfs() {
fill(vis, vis + n + 1, 0);
vector<int> cur, ans;
cur.push_back(1), vis[1] = 1;
for(int i = 0; i < rev_d[1]; i ++) {
vector<int> tmp;
int min_c = INF;
for(int u : cur) {
for(const edge & e : G[u]) if(rev_d[e.to] == rev_d[u] - 1){
min_c = min(min_c, e.c);
}
}
for(int u : cur) {
for(const edge & e : G[u]) if(!vis[e.to] && rev_d[e.to] == rev_d[u] - 1 && e.c == min_c){
tmp.push_back(e.to);
vis[e.to] = 1;
}
}
cur = tmp;
ans.push_back(min_c);
}
cout << rev_d[1] << endl;
for(int i = 0; i < ans.size(); i ++) {
if(i) cout << " ";
cout << ans[i];
}
cout << endl;
}
int main() {
while(cin >> n >> m) {
for(int i = 1; i <= n; i ++) G[i].clear();
while(m --) {
int u, v, c;
cin >> u >> v >> c;
G[u].push_back(edge(v, c));
G[v].push_back(edge(u, c));
}
rev_bfs();
bfs();
}
}四、归纳
- 主要是学习了第二种bfs方法:先通过逆向bfs求出距离d[maxn],然后再进行正向的“bfs”,这样就能实现一些比较复杂的最短路径问题。
- 写代码时,增加时间复杂度系数的写法问题不大的,就像这里代码中先通过一次二重循环找最小颜色,再重复一次把最小颜色的边收集起来,这和写成一个循环时间是差不多的,因此应该怎么方便怎么来。。

京公网安备 11010502036488号