[总结] 补图联通块的求法
[总结] 补图联通块的求法
补图联通块的 链表解法 。
题意
求补图联通块的数量以及每个块内的元素个数。
解题报告
首先考虑一种暴力解法:
每次枚举到当前节点 \(u\) ,都向没有边相连的节点 \(v\) 暴力连边(貌似没有优化建图的方法)。
这么做是 \(\text O(n^2)\) 的,对于 \(n\leq 1e5\) 的图显然会炸。
正解:链表优化
- 核心思想
bfs + 链表排除多余决策。
操作步骤如下:
- 首先找到一个没有在链表里被删除的点 \(i\),将点 \(i\) 加入队列,并新开一个联通块。
- 进行 bfs 操作:
- 取出队头节点 \(u\),遍历 \(u\) 的所有出边指向的节点 \(v\),如果没有在链表里被删除,则打上一个标记为
cover[v]=true
。 - 遍历整个链表。如果当前指针在链表中指向节点没有被打上上一步的标记,那么它可以成为当前联通块中的点,将其在链表中删除并加入队列。否则如果打上了上一步标记,将它标记清空(不用 \(memset\))。
- 取出队头节点 \(u\),遍历 \(u\) 的所有出边指向的节点 \(v\),如果没有在链表里被删除,则打上一个标记为
- 一直更新到链表为空为止,也就是所有的点都被删除了为止。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
const int maxn = 1e5 + 10;
const int maxm = 2e6 + 10;
int nxt[maxn],last[maxn],ans,sc[maxn],n,m;
int head[maxn],cnt=0;
struct edge{
int to,nxt;
}e[maxm<<1];
inline void link(int u,int v){
e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
}
#include <queue>
#define read() read<int>()
inline void del(int x){
nxt[last[x]]=nxt[x];
last[nxt[x]]=last[x];
}
queue<int> q;
bool vis[maxn],cover[maxn];
int main(){
n=read();m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();link(u,v);link(v,u);
}
nxt[0]=1;
for(int i=1;i<n;i++){
nxt[i]=i+1;
last[i+1]=i;
}
for(int i=1;i<=n;i++){
if(vis[i])continue;
vis[i]=true;q.push(i);
del(i);
sc[++ans]=1;
while(q.size()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!vis[v])cover[v]=true;//没有被删除
}
for(int i=nxt[0];i;i=nxt[i]){
if(!cover[i]){
vis[i]=true;
sc[ans]++;
del(i);q.push(i);
}
else cover[i]=false;
}
}
}
sort(sc+1,sc+1+ans);
printf("%d\n",ans);
for(int i=1;i<=ans;i++)printf("%d ",sc[i]);puts("");
return 0;
}