[总结] 补图联通块的求法

[总结] 补图联通块的求法

[POI2007]BIU-Offices

补图联通块的 链表解法

题意

求补图联通块的数量以及每个块内的元素个数。

解题报告

首先考虑一种暴力解法:

每次枚举到当前节点 \(u\) ,都向没有边相连的节点 \(v\) 暴力连边(貌似没有优化建图的方法)。

这么做是 \(\text O(n^2)\) 的,对于 \(n\leq 1e5\) 的图显然会炸。

正解:链表优化

  • 核心思想

bfs + 链表排除多余决策。

操作步骤如下:

  1. 首先找到一个没有在链表里被删除的点 \(i\),将点 \(i\) 加入队列,并新开一个联通块。
  2. 进行 bfs 操作:
    1. 取出队头节点 \(u\),遍历 \(u\) 的所有出边指向的节点 \(v\)如果没有在链表里被删除,则打上一个标记为 cover[v]=true
    2. 遍历整个链表。如果当前指针在链表中指向节点没有被打上上一步的标记,那么它可以成为当前联通块中的点,将其在链表中删除并加入队列。否则如果打上了上一步标记,将它标记清空(不用 \(memset\))。
  3. 一直更新到链表为空为止,也就是所有的点都被删除了为止。
#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;
}
posted @ 2021-08-12 18:30  ¶凉笙  阅读(176)  评论(0编辑  收藏  举报