[BZOJ 1098] [POI2007] 办公楼biu 【链表优化BFS】
题目链接:BZOJ - 1098
题目分析
只有两个点之间有边的时候它们才能在不同的楼内,那么就是说如果两个点之间没有边它们就一定在同一座楼内。
那么要求的就是求原图的补图的连通块。
然而原图的补图的边数是 n^2 级别的,非常庞大,我们不能直接把补图求出来。
可以使用一种用链表优化BFS的做法,开始时将所有的点加到一个链表里。
每次找一个连通块的时候BFS,在链表中取出一个点,在链表中删除,加入队列,然后每次取出队首元素x,枚举x的每一条边,将边的终点y从链表中删去,加到一个临时的链表中存储。
这样枚举完 x 的所有边之后,原链表里剩余的点就是与 x 没有边的,这些点在补图里与 x 就是有边的,将这些点加入队列。
然后用临时的链表替代原链表,原链表中就是剩下的点了。
这样BFS几次就可以求出所有连通块了。
每一个点都只被从链表中删去1次,每条边都只遍历了一次,总的时间复杂度是 O(n + m)。
代码
#include <iostream> #include <cstdlib> #include <cstring> #include <cmath> #include <cstdio> #include <algorithm> #include <queue> using namespace std; const int MaxN = 100000 + 5, MaxM = 2000000 + 5; inline void Read(int &Num) { char c; c = getchar(); while (c < '0' || c > '9') c = getchar(); Num = c - '0'; c = getchar(); while (c >= '0' && c <= '9') { Num = Num * 10 + c - '0'; c = getchar(); } } int n, m, Top, R1, R2, Sum; int Ans[MaxN], Last[MaxN], Next[MaxN]; bool InList[MaxN]; struct Edge { int v; Edge *Next; } E[MaxM * 2], *P = E, *Point[MaxN]; inline void AddEdge(int x, int y) { ++P; P -> v = y; P -> Next = Point[x]; Point[x] = P; } queue<int> Q; inline void Add(int x, int y) { Next[y] = Next[x]; if (Next[x]) Last[Next[x]] = y; Next[x] = y; Last[y] = x; } inline void Delete(int x) { if (Last[x]) Next[Last[x]] = Next[x]; if (Next[x]) Last[Next[x]] = Last[x]; } void BFS() { while (!Q.empty()) Q.pop(); Q.push(Next[R1]); InList[Next[R1]] = false; Delete(Next[R1]); int x, y; ++Top; while (!Q.empty()) { x = Q.front(); ++Sum; ++Ans[Top]; Q.pop(); R2 = n + 2; Last[R2] = Next[R2] = 0; for (Edge *j = Point[x]; j; j = j -> Next) { y = j -> v; if (!InList[y]) continue; Delete(y); Add(R2, y); } for (int i = Next[R1]; i; i = Next[i]) { Q.push(i); InList[i] = false; } Next[R1] = Next[R2]; Last[Next[R1]] = R1; } } int main() { scanf("%d%d", &n, &m); int a, b; for (int i = 1; i <= m; ++i) { Read(a); Read(b); AddEdge(a, b); AddEdge(b, a); } Top = 0; Sum = 0; R1 = n + 1; for (int i = 1; i <= n; ++i) Add(R1, i); for (int i = 1; i <= n; ++i) InList[i] = true; while (Sum < n) BFS(); printf("%d\n", Top); sort(Ans + 1, Ans + Top + 1); for (int i = 1; i <= Top; ++i) printf("%d ", Ans[i]); return 0; }