例题6-20 UVA1599 Ideal Path(67行AC代码)

紫书刷题进行中,题解系列【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;
}
发布了128 篇原创文章 · 获赞 87 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_40738840/article/details/104426003