题库连接c++
给你一个 \(n\) 个点 \(m\) 条边的无向图,求其补图的连通块个数及各个连通块大小。算法
\(1\leq n,m\leq 200000\)spa
参考了 ww140142 的作法。题解也转自该博客。.net
每次枚举一个未处理过的点,而后从它开始宽搜出它所在的连通块;code
具体是枚举它的全部原图的边,标记起来,枚举边以后再枚举全部的点,将未标记的点加入该连通块,并加入队列继续宽搜;blog
为了节约无用的枚举,咱们还须要对全部点构建链表,将已经在某个块内的点删除;队列
这个算法的复杂度是 \(O(n+m)\) 的;ip
缘由是每个点仅进行了一次宽搜的拓展;get
而且在每次拓展中,枚举边表总复杂度是 \(O(m)\) ;博客
而以后的枚举剩下的点,咱们将点分为两部分:已标记的点的复杂度计在 \(O(m)\) 以内,而未标记的点将会被加入队列,这个过程对每一个点也仅有一次。
综上复杂度为 \(O(n+m)\) 。
#include <bits/stdc++.h> using namespace std; const int N = 200000; int n, m, u, v; vector<int>to[N+5]; queue<int>Q; int lst[N+5], nxt[N+5], ans[N+5], cnt; int vis[N+5], undo[N+5]; void delet(int x) {nxt[lst[x]] = nxt[x], lst[nxt[x]] = lst[x]; } void work() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) scanf("%d%d", &u, &v), to[u].push_back(v), to[v].push_back(u); for (int i = 1; i < n; i++) nxt[i] = i+1, lst[i+1] = i; nxt[0] = 1; for (int i = 1; i <= n; i++) if (vis[i] == 0) { ans[++cnt] = 1; vis[i] = 1, Q.push(i); delet(i); while (!Q.empty()) { u = Q.front(); Q.pop(); for (int j = 0, sz = to[u].size(); j < sz; j++) if (vis[to[u][j]] == 0) undo[to[u][j]] = 1; for (int j = nxt[0]; j; j = nxt[j]) if (undo[j] == 0) {vis[j] = 1, ++ans[cnt]; delet(j); Q.push(j); } else undo[j] = 0; } } sort(ans+1, ans+cnt+1); printf("%d\n", cnt); for (int i = 1; i <= cnt; i++) printf("%d ", ans[i]); } int main() {work(); return 0; }