AT_abc481_f题解

笔记列表

参考了官方题解的做法,使用 Dijkstra 求最短路。

简化题意

给定一张有向图,每条边带一个 \(1 \sim 9\) 的数字。从点 \(1\) 到点 \(v\) 的路径分数,是把沿途边上的数字依次拼接成的十进制整数(点和边可以重复经过)。对每个 \(v=2,3,\dots,N\),求从点 \(1\) 到点 \(v\) 的最小可能分数,并输出其对 \(998244353\) 取模的结果。

  • 为什么可以使用 Dijkstra?
    • 显然每走一条边分数都会变大,相当于边权非负。
    • 只需要保留最小分数:如果父节点的分数更小,那么后面接上什么数字,都会比从更大分数接上数字更小。

但是注意,不能直接用 Dijkstra 记录每个点的真实分数,因为 \(N \le 2\times 10^5\),形成的数字会非常长,无法直接比较。

可以发现,可以巧妙地用排名替代一个点的真实分数。设父节点是 \(u\),当前点是 \(v\),边上的数字为 \(w\),则真实分数满足

\[S_v = 10S_u + w. \]

这个值只和父节点的分数以及边上的数字有关。于是把分数记录成排名,新分数的大小就可以由二元组

\[(\text{父节点分数排名},\ \text{边上的数字}) \]

比较。

这里的排名,就是把所有出现过的分数按从小到大排序后得到的编号。分数越小,排名越小。

由于优先队列按二元组从小到大弹出,当前弹出的分数排名不会小于上一个弹出的分数排名。因此维护排名时,只需要看当前二元组和上一个弹出的二元组是否相同即可。

这份代码中的 Dijkstra 写法和普通 Dijkstra 写法不一样。普通 Dijkstra 会记录 \(dis\),然后用当前点依次松弛相邻边来获得距离。这里参考了官方题解的写法:在堆的一次弹出时确定这个点的距离。这样当然也是正确的,因为一个点从优先队列里弹出的时候,能更新它的点都已经处理完了。

然后按正常 Dijkstra 的思路处理即可。

#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int mod = 998244353;
const int N = 2e5 + 10;
int n, m;
vector<pair<int,int>> G[N];
int vis[N], dis[N];
struct Q {
    int frk, fath, v, nowdis; // 按照父节点排名、边上的数字排序节点
    bool operator<(const Q& o) const {
        if(frk != o.frk) return frk > o.frk;
        if(fath != o.fath) return fath > o.fath;
        if(v != o.v) return v > o.v;
        return nowdis > o.nowdis;
    } 
};
priority_queue<Q> q;

signed main(){
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    cin >> n >> m;
    fore(i, 1, m) {
        int u, v, w;
        cin >> u >> v >> w;
        G[u].push_back({v, w});
    }
    int Rktot = 0;
    pair<int,int> Lst = {-1, -1};
    for(pair<int,int> e : G[1]) {
        q.push({0, e.second, e.first, e.second});
    }
    dis[1] = 0;
    vis[1] = 1;
    while(!q.empty()) {
        Q now = q.top();
        q.pop();
        int u = now.v;
        if(vis[u]) continue;
        vis[u] = 1;
        int fath = now.fath;
        int frk = now.frk;
        dis[u] = now.nowdis;
        if(Lst != pair<int,int>{frk, fath}) { 
            Lst = {frk, fath};
            Rktot ++;
            // 如果这个点的排名更大,说明来了新的排名
        }
        for(pair<int,int> e : G[u]) {
            int v = e.first, w = e.second;
            q.push({Rktot, w, v, (dis[u] * 10 + w) % mod});
        }
    }
    fore(i, 2, n) cout << dis[i] << '\n';
    return 0;
}
posted @ 2026-10-29 11:35  wmq2012  阅读(6)  评论(0)    收藏  举报