[NOIP2013 提高组] 货车运输 题解
[NOIP2013 提高组] 货车运输 题解
本题解介绍一种 最大生成树+并查集+启发式合并离线 的做法。
想法
题目要多次求两点之间的最大瓶颈路长度,所以可以先参照最小瓶颈路的通常求法构造出一个最大生成树,这样在这棵最大生成树上的任意两点之间的距离即为它们最大瓶颈路的距离。
在此处出现了许多做法,可以使用 \(\text{LCA}\) 快速求解这个树上问题,不过其实还有一种新奇的思路。
思路
考虑在 \(\text{Kruskal}\) 算法求解最大生成树的时候,把询问离线地挂在点上,如果在 \(\text{merge(a, b)}\) 操作的时候发现 \(a\) 所在的集合中有合并是到 \(b\) 所在的集合的,且之前没有处理过,那么此时一定是最优解,直接把此次合并操作的边权 \(w_{a\rightarrow b}\) 置为那次询问的结果。
如果只是朴素做法的话这道题中会超时,可以考虑采用启发式合并——把询问次数少的集合合并到询问次数多的集合中,进行优化。
总时间复杂度:\(O(m\log m + q)\)。
// Problem: P1967 [NOIP2013 提高组] 货车运输
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1967
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2022-12-26 20:15:07
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10, M = 1e5 + 10, Q = 3e4 + 10;
struct qwq
{
int a, b, c;
} edge[M];
int read(){...}
int fa[N], ans[Q];
vector<PII> qs[N]; // 点上的查询
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int a, int b, int w)
{
int x = find(a), y = find(b);
if (x == y)
return;
if (qs[x].size() > qs[y].size())
swap(x, y);
fa[x] = y;
for (auto i : qs[x])
{
int j = i.x, id = i.y;
if (~ans[id]) // 已经有解了不用更新,即使此次有解也肯定没有之前的优
continue;
if (find(j) == y) // 如果此次有解
ans[id] = w; // 将查询置为这个解
}
for (auto i : qs[x]) // 合并查询
if(!~ans[i.y])
qs[y].push_back(i);
}
int main()
{
memset(ans, -1, sizeof ans);
int n = read(), m = read();
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1; i <= m; i++)
{
int a = read(), b = read(), c = read();
edge[i] = {a, b, c};
}
sort(edge + 1, edge + m + 1, [](qwq a, qwq b) { return a.c > b.c; }); // 从大到小排序
int q = read();
for (int i = 1; i <= q; i++)
{
int a = read(), b = read();
qs[a].push_back({b, i});
qs[b].push_back({a, i});
}
for (int i = 1; i <= m; i++)
merge(edge[i].a, edge[i].b, edge[i].c);
for (int i = 1; i <= q; i++)
printf("%d\n", ans[i]);
return 0;
}