国际旅行 Ⅱ
国际旅行 Ⅱ
题目描述
小龙突然穿越到异世界这里的国家划分并不同于之前的世界,具体的目前有 个城市, 条双向道路,确保所有城市联通,每个国家由若干个城市组成,若城市 和城市 之间存在至少两条路径所包含的边不具有交集,那么 和 属于同一个国家,否则属于不同的国家。
每条道路都需要花费一定的时间通过,若一条道路连接的两个城市属于不同国家,则该道路被称为国际道路。现在小龙提出 个问题,每个问题他想知道从城市 出发,只经过花费时间不超过 的国际道路(可以经过非国际道路,且没有限制),所到达的国家中城市个数第 小的国家所拥有的城市个数。
输入描述:
第一行包含两个正整数 代表 个城市和 条双向道路。
接下来 行,每行包含三个正整数 表示连接 的道路需要花费 个单位时间通过。
接下来一行一个整数 接下来 行每行三个整数 代表从城市 出发经过不超过 个单位时间的道路,所到达的国家中城市个数第 小的国家。, 确保给出的图不具有重边和自环。
输出描述:
输出 行,每行代表第 问题的答案,如果不存在拥有城市个数第 小的国家则输出 。
示例1
输入
5 5
1 2 4
2 3 1
3 4 1
2 4 1
4 5 3
3
5 3 1
5 3 2
5 4 3
输出
1
3
3
说明
图中一共有三个国家 拥有的城市个数分别是 1 3 1
对于第一个询问:
从 5 出发只经过权值不超过3的国际道路所到达的国家所拥有的城市数量是:1,3
所以第 1 小的是 1 对于第二个询问:从5出发只经过权值不超过 3 的国际道路所到达的国家所拥有的城市数量是:1,3
所以第 2 小的是 3 对于第三个询问:从5出发只经过权值不超过 4 的国际道路所到达的国家所拥有的城市数量是:1,1,3
所以第 3 小的是 3
解题思路
若两个点间存在两条没有交集的路径,说明这两个点在同一个边双连通分量内(参考冗余路径)。为此我们可以先跑一遍 Tarjan 算法进行缩点,并找出所有的桥。缩完点后,连通分量和桥就会构成一棵树,每个连通分量(以下称为节点)就是一个国家,而城市数量就是该连通分量内点的数量(以下称为节点的权值)。
对于每个询问,显然我们可以通过并查集维护权值不超过 的桥所构成的图,并给每个连通块开一个平衡树来维护节点权值的有序性。最后看 对应节点所在的连通块内是否至少有 个节点,如果有则输出第 小的权值即可。显然如果每个询问都这么做会超时,又发现如果我们维护出了权值不超过 的桥构成的图 ,当需要维护权值不超过 的桥构成的图 时,本质是在 的基础上再把权值在权值在 的桥加入到图中。这启发我们可以按照 从小到大对询问进行离线处理,并按桥的权值动态维护图的连通性。
上面提到为了维护连通块内权值的有序性需要开平衡树,且要支持查询第 小的数,因此需要用到 PBDS,但博主并不会用。官方题解给出的一种做法是用权值线段树来维护,但两个连通块合并时会涉及到线段树合并,博主也不会。所以最后选了一种比较讨巧的做法。注意到所有节点的权值和恰好等于 (然而比赛时并没有注意到),说明最多有 种权值不同的节点。这就可以暴力了,给每个连通块开一个 std::map<int, int>
存储每种权值的数量。查询时按权值从小到大直接暴力枚举 std::map<int, int>
内的元素,从而找到第 小的权值,时间复杂度是 。合并连通块时也是直接枚举其中一个连通块内的元素并加入到另外一个连通块中即可,时间复杂度是 。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, M = N * 2;
int h[N], e[M], ne[M], wt[M], idx;
int dfn[N], low[N];
int stk[N], tp;
int id[N], sz[N], cnt;
vector<array<int, 3>> p;
array<int, 4> q[N];
int fa[N];
map<int, int> mp[N];
int ans[N];
void add(int u, int v, int w) {
e[idx] = v, wt[idx] = w, ne[idx] = h[u], h[u] = idx++;
}
void tarjan(int u, int f) {
dfn[u] = low[u] = ++idx;
stk[++tp] = u;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
tarjan(v, i);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) p.push_back({wt[i], u, v});
}
else if (i != (f ^ 1)) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
cnt++;
int t;
do {
t = stk[tp--];
id[t] = cnt;
sz[cnt]++;
} while (t != u);
}
}
int find(int x) {
return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
cin >> n >> m;
memset(h, -1, sizeof(h));
while (m--) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w), add(v, u, w);
}
idx = 0;
tarjan(1, -1);
sort(p.begin(), p.end());
cin >> k;
for (int i = 0; i < k; i++) {
cin >> q[i][1] >> q[i][0] >> q[i][2];
q[i][3] = i;
}
sort(q, q + k);
for (int i = 1; i <= cnt; i++) {
fa[i] = i;
mp[i][sz[i]]++;
}
memset(ans, -1, sizeof(ans));
for (int i = 0, j = 0; i < k; i++) {
while (j < p.size() && p[j][0] <= q[i][0]) {
int x = find(id[p[j][1]]), y = find(id[p[j][2]]);
j++;
if (x == y) continue;
for (auto &it : mp[y]) {
mp[x][it.first] += it.second;
}
fa[y] = x;
}
int x = find(id[q[i][1]]), cnt = 0;
for (auto &[x, y] : mp[x]) {
cnt += y;
if (cnt >= q[i][2]) {
ans[q[i][3]] = x;
break;
}
}
}
for (int i = 0; i < k; i++) {
cout << ans[i] << '\n';
}
return 0;
}
参考资料
河南萌新联赛2024第(二)场:南阳理工学院:https://www.nowcoder.com/discuss/645682390728269824
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18325725
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效