P9432 [NAPC-#1] rStage5 - Hard Conveyors

P9432 [NAPC-#1] rStage5 - Hard Conveyors

感谢此题让我知道了 Dijkstra 的一种新用法。

题意:

给定一棵 \(n\) 个节点的无根树以及树上的 \(k\) 个关键节点,给定边的长度。有 \(q\) 次询问,每次给出 \(s,t\),问从 \(s\)\(t\) 且经过至少一个关键节点的路径的最短长度。

分析:

显然经过至少一个关键节点的条件可以分为两种情况判断:

  1. 如果从 \(s\)\(t\)简单路径上已经有关键点,那么直接输出 \(s\)\(t\) 的简单路径长度即可。这个过程显然可以以 \(1\) 为根处理出每个节点 \(u\) 到节点 \(1\) 的距离 \(len_u\),那么长度 \(ans\) 就是 \(len_u+len_v-2\times len_{lca(u,v)}\)

  2. 如果从 \(s\)\(v\)简单路径上没有关键点,那么显然找到最近的关键点 \(v\),走到 \(v\) 再回到简单路径上的方法可以得到最优解。而不妨预处理出每个点到任意一个关键点的最短距离 \(dis\) 数组,则路径到任意关键点的最短距离就可以定义为简单路径上 \(dis\) 数组的最小值 \(mindis\),由此答案只需要在 \(ans\) 的基础上再加上 \(2\times mindis\)。而多次查询路径上区间有关信息的问题显然可以用线段树 \(+\) 树链剖分实现,那么关键就是如何计算每个点到任意一个关键点的最短距离

这时候就需要 Dijkstra 的经典(神奇逆天)多源最短路写法了:将每个关键点的初始 \(dis\) 均设为 \(0\),再跑普通的堆优化 Dijkstra,此时 \(dis\) 数组存储的就是每个点到任意一个关键点的最短距离了。

核心代码如下:

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});
			}
		}
	}
}

剩下的就是经典的普通树剖维护 \(dis\) 数组的区间最小值了。

#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";
	}
}
posted @ 2023-09-27 18:35  Happy-Pig-Orz  阅读(31)  评论(0编辑  收藏  举报