题意:
给出一个图,求其补图的连通块个数与大小;
原图点数n<=100000,m<=2000000;
题解:
这道题主要是对于复杂度的优化分析上吧;
一开始有个显然的O(n^2*α(n))暴力做法,枚举每一条补图中的边然后并查集啦;
这样空间与时间都是无法接受的;
考虑另一种做法:每次枚举一个未在连通块的点,然后从它开始宽搜出它所在的连通块;
具体是枚举它的所有原图的边,标记起来,枚举边之后再枚举所有的点,将未标记的点加入该连通块,并加入队列继续宽搜;
为了节约无用的枚举,我们还需要对所有点构建链表,将已经在某个块内的点删除;
这个算法的复杂度是O(n+m)的;
原因是每一个点仅进行了一次宽搜的拓展;
并且在每次拓展中,枚举边表总复杂度是O(m);
而之后的枚举剩下的点,我们将点分为两部分:已标记的点的复杂度计在O(m)之内,而未标记的点将会被加入队列,这个过程对每个点也仅有一次;
证明了复杂度就结束了,可以放心大胆的去*这道题啦;
代码:
#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110000
#define M 2100000
using namespace std;
int enext[M<<1],to[M<<1],head[N],ce;
int st[N],last[N],next[N],top;
bool vis[N],cov[N];
queue<int>q;
void add(int x,int y)
{
to[++ce]=y;
enext[ce]=head[x];
head[x]=ce;
}
void del(int x)
{
next[last[x]]=next[x];
last[next[x]]=last[x];
}
int main()
{
int n,m,i,j,k,x,y,ans;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
for(i=1;i<n;i++)
next[i]=i+1,last[i+1]=i;
next[0]=1;
for(i=1;i<=n;i++)
{
if(!vis[i])
{
st[++top]=1;
vis[i]=1;
q.push(i);
del(i);
while(!q.empty())
{
x=q.front(),q.pop();
for(j=head[x];j;j=enext[j])
{
if(!vis[to[j]])
{
cov[to[j]]=1;
}
}
for(j=next[0];j;j=next[j])
{
if(!cov[j])
{
vis[j]=1;
st[top]++;
del(j);
q.push(j);
}
else
cov[j]=0;
}
}
}
}
sort(st+1,st+top+1);
printf("%d\n",top);
for(i=1;i<=top;i++)
printf("%d ",st[i]);
return 0;
}