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;
}
本文来自博客园,作者:wmq2012,转载请注明原文链接:https://www.cnblogs.com/wmq2012/p/23478853

浙公网安备 33010602011771号