洛谷 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
*/

浙公网安备 33010602011771号