在一些题目中需要不断地将一些点合并为一个点,同时也就带来了麻烦:如何高效地维护邻居集合?
我们可以将邻边启发式合并,其复杂度可以做到 O ( m log n ) O(m \log n) O(mlogn)。
具体来说,在访问时,从邻接表的末端向前扫描,如果边的两个端点已经在同一集合里,就将这条边删了。如此可以保证访问次数和边数基本相等。
在合并两个点集时,将边数小的点集合并到边数大的点集里去,可以证明,该复杂度为 O ( m log n ) O(m \log n) O(mlogn)。
几点注意
- 操作时,我们只需要考虑各个集合的根,也就是说所有的信息都是保存在该集合的根节点上的。
- 在并查集与优先队列一起用时,我们不能轻易的 s w a p swap swap节点,而是应该 s w a p swap swap信息。这是因为当你 s w a p swap swap节点时,优先队列里可能就出现的同一节点的新旧两个版本;而你 s w a p swap swap信息时,优先队列里每个根节点的信息都是唯一的。
例题:CF1515F Phoenix and Earthquake
#include <bits/stdc++.h>
#define MP make_pair
using namespace std;
#define rep(i, n) for (int i = 0; i < (int)(n); ++ i)
#define rep1(i, n) for (int i = 1; i <= (int)(n); ++ i)
#define foreach(itr, c) for (__typeof((c).begin()) itr = (c).begin(); itr != (c).end(); ++ itr)
typedef long long LL;
typedef pair<int, int> pii;
const int N = 3e5 + 5;
int n, m;
LL K, a[N], sum;
vector<pii> G[N];
struct DSU
{
int fa[N], siz[N];
void init(){ rep1(i, n) fa[i] = i, siz[i] = 1; }
int find(int x){ return x == fa[x] ? x : fa[x] = find(fa[x]); }
bool same(int x, int y){ return find(x) == find(y); }
void merge(int x, int y)
{
x = find(x), y = find(y);
if (x == y) return ;
fa[y] = fa[x], siz[x] += siz[y]; // merge时不需要启发式
}
} dsu;
int main()
{
scanf("%d%d%lld", &n, &m, &K);
rep1(i, n) scanf("%lld", &a[i]);
rep1(i, m){
int u, v; scanf("%d%d", &u, &v);
G[u].push_back(MP(v, i)), G[v].push_back(MP(u, i));
}
rep1(i, n) sum += a[i];
if (sum < 1ll * (n - 1) * K){
printf("NO\n");
return 0;
}
priority_queue<pair<LL, int> > pq;
rep1(i, n) pq.push(MP(a[i], i));
printf("YES\n");
dsu.init();
rep(cnt, n - 1){
int u = pq.top().second; pq.pop();
while (dsu.find(u) != u){ // 只需要考虑根节点
u = pq.top().second;
pq.pop();
}
while (dsu.same(G[u].back().first, u)) // 从后向前遍历邻接表
G[u].pop_back(); // 如果两端点在同一集合,就删除该边
int v = dsu.find(G[u].back().first);
printf("%d\n", G[u].back().second);
dsu.merge(u, v); // 不改变根节点
a[u] += a[v] - K;
pq.push(MP(a[u], u));
if (G[u].size() < G[v].size()) swap(G[u], G[v]); // swap信息,非节点
G[u].insert(G[u].end(), G[v].begin(), G[v].end()); // 合并边集
G[v].clear(); // 注意清空
}
return 0;
}