洛谷 P3452 [POI2007]BIU-Offices 题解

简化题意:求原图的补图的连通块个数。

众所周知,补图有这样一条性质:若图不连通,则其补图一定连通。证明略。

于是我们考虑枚举满足 $vis_{\text{u}}=0$ 的点 $\text{u}$,然后将其 push 进队列并打上标记(vis)。然后枚举与 $\text{u}$ 在补图上相邻的且 $vis_{\text{v}}=0$ 的点 $\text{v}$,再将其加入队列并打标记······如此反复直至队列为空为止。

我们考虑优化,结合链表的思想,我们可以维护出每个点之后第一个未被标记的点,然后向后跳即可找到所有 $\text{v}$。如此我们还需维护删除操作,所以用链表来实现。

时间复杂度 $O(n+m)$。

//P3452 [POI2007]BIU-Offices
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int MAXN=100010;
/***********图论***********/
struct Edge
{
	int to,nxt;
}e[4000100];
int cnt,head[MAXN];
bool vis[MAXN],exi[MAXN];
queue<int> q;
void addedge(int x,int y)
{
	e[++cnt].nxt=head[x];
	head[x]=cnt;
	e[cnt].to=y;
	return;
}
/***********链表***********/
int l[MAXN]/*pre*/,r[MAXN]/*nxt*/;
void del(int x)
{
	r[l[x]]=r[x];
	l[r[x]]=l[x];
	return;
}
/***********Main***********/
int n,m,ans[MAXN],sum;
int main()
{
	int x,y,u,v;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		addedge(x,y);
		addedge(y,x);
	}
	r[0]=1;
	for(int i=1;i<n;i++)
	{
		l[i+1]=i;
		r[i]=i+1;
	}
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			ans[++sum]=1;//新开连通块
			q.push(i);
			vis[i]=true;
			del(i);
			while(!q.empty())
			{
				u=q.front();
				q.pop();
				for(int j=head[u];j;j=e[j].nxt)
				{
					v=e[j].to;
					if(!vis[v])
					{
						exi[v]=true;//如果原图可以到达的点没有访问过,则标记为可以访问
					}
				}
				for(int j=r[0];j;j=r[j])
				{
					if(!exi[j])//访问不到的全都入队,因为在补图里可以访问到
					{
						vis[j]=true;
						ans[sum]++;
						del(j);
						q.push(j);
					}
					else
					{
						exi[j]=false;
					}
				}
			}
		}
	}
	sort(ans+1,ans+sum+1);
	printf("%d\n",sum);
	for(int i=1;i<=sum;i++)
	{
		printf("%d ",ans[i]);
	}
	return 0;
}
/*
 * 洛谷
 * https://www.luogu.com.cn/problem/P3452
 * C++20 -O2
 * 2022.9.14
 */
posted @ 2022-09-14 21:45  Day_Dreamer_D  阅读(334)  评论(0)    收藏  举报