P9432 [NAPC-#1] rStage5 - Hard Conveyors
P9432 [NAPC-#1] rStage5 - Hard Conveyors
感谢此题让我知道了 Dijkstra
的一种新用法。
题意:
给定一棵 个节点的无根树以及树上的 个关键节点,给定边的长度。有 次询问,每次给出 ,问从 到 且经过至少一个关键节点的路径的最短长度。
分析:
显然经过至少一个关键节点的条件可以分为两种情况判断:
-
如果从 到 的简单路径上已经有关键点,那么直接输出 到 的简单路径长度即可。这个过程显然可以以 为根处理出每个节点 到节点 的距离 ,那么长度 就是 。
-
如果从 到 的简单路径上没有关键点,那么显然找到最近的关键点 ,走到 再回到简单路径上的方法可以得到最优解。而不妨预处理出每个点到任意一个关键点的最短距离 数组,则路径到任意关键点的最短距离就可以定义为简单路径上 数组的最小值 ,由此答案只需要在 的基础上再加上 。而多次查询路径上区间有关信息的问题显然可以用线段树 树链剖分实现,那么关键就是如何计算每个点到任意一个关键点的最短距离。
这时候就需要 Dijkstra
的经典(神奇逆天)多源最短路写法了:将每个关键点的初始 均设为 ,再跑普通的堆优化 Dijkstra
,此时 数组存储的就是每个点到任意一个关键点的最短距离了。
核心代码如下:
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
inline void dij() {
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
for (int i = 1; i <= k; i++) {
dis[p[i]] = 0;
q.push({0, p[i]});
}
while (!q.empty()) {
auto u = q.top().second;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto i : G[u]) {
int v = i.first, w = i.second;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push({dis[v], v});
}
}
}
}
剩下的就是经典的普通树剖维护 数组的区间最小值了。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5 + 5;
int n, m, k;
vector<pair<int, int> > G[maxn];
struct data {
int l, r, dat;
} t[maxn << 2];
int dep[maxn], fa[maxn], siz[maxn], son[maxn], top[maxn], dfn[maxn], rk[maxn], cnt;
int len[maxn];
int dis[maxn], p[maxn];
bool vis[maxn];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
inline void dfs1(int u, int f) {
dep[u] = dep[f] + 1;
siz[u] = 1;
for (auto i : G[u]) {
int v = i.first, w = i.second;
if (v == f) continue;
fa[v] = u;
len[v] = len[u] + w;
dfs1(v, u);
siz[u] += siz[v];
if (siz[son[u]] < siz[v]) son[u] = v;
}
}
inline void dfs2(int u, int tp) {
top[u] = tp;
dfn[u] = ++cnt;
rk[cnt] = u;
if (!son[u]) return;
dfs2(son[u], tp);
for (auto i : G[u]) {
int v = i.first;
if (v == fa[u] or v == son[u]) continue;
dfs2(v, v);
}
}
inline void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r) {
t[p].dat = dis[rk[l]];
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
t[p].dat = min(t[p << 1].dat, t[p << 1 | 1].dat);
}
inline int query(int p, int l, int r) {
if (l <= t[p].l and t[p].r <= r) return t[p].dat;
int mid = (t[p].l + t[p].r) >> 1, res = 0x3f3f3f3f;
if (l <= mid) res = min(res, query(p << 1, l, r));
if (mid < r) res = min(res, query(p << 1 | 1, l, r));
return res;
}
inline void dij() {
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
for (int i = 1; i <= k; i++) {
dis[p[i]] = 0;
q.push({0, p[i]});
}
while (!q.empty()) {
auto u = q.top().second;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto i : G[u]) {
int v = i.first, w = i.second;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push({dis[v], v});
}
}
}
}
inline int get(int u, int v) {
int res = 0x3f3f3f3f, ans = 0, uu = u, vv = v;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
res = min(res, query(1, dfn[top[u]], dfn[u]));
u = fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
res = min(res, query(1, dfn[u], dfn[v]));
ans = len[uu] + len[vv] - len[u] * 2;
return ans + res * 2;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
G[u].push_back({v, w});
G[v].push_back({u, w});
}
for (int i = 1; i <= k; i++) cin >> p[i];
dij();
dfs1(1, 1);
dfs2(1, 1);
build(1, 1, n);
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
cout << get(u, v) << "\n";
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】