P10648 题解
感谢机房大佬 @Jeslan 的指教。
首先读题,发现题中说“一但同意了联盟,则两个岛屿所在联盟立即并成一个联盟”,那么首先想到的做法就是并查集,那么,这题合并联盟的部分就做完了。这么简单?(bushi)
接下来就是这题的难点:如何维护每个联盟不能结成联盟的岛屿?在上面的并查集中,我们每次合并都会选定一个点为该并查集的代表,那么,可以想到,在合并联盟时把所有非代表结点所不能结盟的岛屿都移至代表上,这样在合并时就只需考虑两个待合并联盟的代表了,下面来举个栗子:
上图中,双向边所连接着的是敌对的岛屿。若此时把岛屿
若此时再将
此时若将
算法的思想就到这了,下面来说说实现。
本题的核心就在于转移敌对边,普通 vector
或链式前向星存图无法做到高效地转移边,那么就考虑使用 set
保存边,合并时使用启发式合并。
奉上代码一份:
#include <bits/stdc++.h> #include <bits/extc++.h> using namespace std; using namespace __gnu_pbds; typedef long long ll; typedef long double ld; typedef unsigned long long ull; typedef pair<int, int> pii; const int MAXN = 1e5 + 5; int fa[MAXN]; int getfa(int x) { if (fa[x] == x) return x; return fa[x] = getfa(fa[x]); } void merge(int x, int y) { fa[getfa(x)] = getfa(y); } //以上两个并查集核心函数 set<int> s[MAXN];//set 保存每个岛屿/联盟所敌对的岛屿/联盟 int n, m, q; int main() { cin >> n >> m >> q; for (int i = 0; i <= n; i++) { fa[i] = i; }//初始化并查集 for (int i = 1; i <= m; i++) { int u, v; cin >> u >> v; s[u].insert(v); s[v].insert(u);//互相敌对 } for (int i = 1; i <= q; i++) { int u, v; cin >> u >> v; int fau = getfa(u); int fav = getfa(v); if (s[fau].size() > s[fav].size()) swap(fau, fav);//启发式合并,小的合并入大的 if (s[fau].find(fav) == s[fau].end()) {//如果找不到 father of u,即 u 与 v 不敌对,此时可合并 printf("APPROVE\n"); for (auto x : s[fau]) {//将小集合所有敌对边转移到代表上 s[fav].insert(x); s[x].erase(fau);//删除原有边 s[x].insert(fav); } merge(fau, fav);//并查集合并 } else { printf("REFUSE\n"); } } return 0; }
本文作者:Cuset_VoidAldehyde
本文链接:https://www.cnblogs.com/CusetVoidAldehyde/p/18269103
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步