P3452 [POI2007]BIU-Offices 题解

Description

给定一张 n n n 个点 m m m 条边的图,你需要求出其补图的连通块个数以及各个连通块的大小。

原数据范围: 1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 2 × 1 0 6 1 \le n \le 10^5,1 \le m \le 2 \times 10^6 1n105,1m2×106
加强版数据范围: 1 ≤ n , m ≤ 5 × 1 0 6 1 \le n,m \le 5 \times 10^6 1n,m5×106

Solution

补图的边数是 n 2 n^2 n2 级别的,若我们建出整个补图则无法通过。

注意到,我们只需要连通块的信息,所以求出补图上各个连通块的生成树就足够了。该选择什么生成树呢?试试 bfs \text{bfs} bfs 树!

不难得到下面的大体思路——枚举一个 vis \text{vis} vis 0 0 0 的点 u u u,标记 vis u = 1 \text{vis}_u=1 visu=1 并将它放入队列。然后,我们枚举 u u u 在补图上相邻且 vis \text{vis} vis 0 0 0 v v v,再将 v v v 加入队列,以此类推。仔细分析复杂度,不难发现——若能够快速找到所有这样的点 v v v,并仅枚举它们,那么所涉及到的边就只有树边,复杂度就正确了。

结合链表的思想,维护出每个点之后第一个未被打标记的点,于是每次从 u u u 往后跳就能找到所有 v v v 了。注意到,我们还需维护删除操作,所以需要使用链表维护。

特别的,若此时 v v v u u u 相邻,那么我们就忽略这个 v v v 并继续向后跳;所以对于每个 u u u,还要将它在原图上的相邻点打标记,便于在跳动过程中判断 v v v 是否合法。虽然这样并没有彻底做到 ⌈ \lceil 涉及到的只有树边 ⌋ \rfloor ,但新增的一部分复杂度等于枚举原图上的边的复杂度,是 O ( m ) O(m) O(m) 的。

本题被解决。总复杂度 O ( n + m ) O(n+m) O(n+m)

Code

建议非紧急情况不要阅读代码,因为这与 Solution 中的某些定义不符

#include <bits/stdc++.ducati>
#define int long long
using namespace std;
const int maxn=100005,maxm=2000005;

int read(){
    
    
	int s=0,w=1;char ch=getchar();
	while (ch<'0'||ch>'9'){
    
    if (ch=='-')  w=-w;ch=getchar();}
	while (ch>='0'&&ch<='9'){
    
    s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
	return s*w;
}
int n,m,l,r,len,cnt;
int head[maxn],frt[maxn],nxt[maxn],vis[maxn],q[maxn],ans[maxn];

struct edge{
    
    int nxt,to;}e[maxm<<1];
 
void add_edge(int u,int v){
    
    
	cnt++;
	e[cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;
}

void del(int now){
    
    
	frt[nxt[now]]=frt[now];
	nxt[frt[now]]=nxt[now];
}

signed main(){
    
    
	n=read(),m=read();
	for (int i=0;i<=n;i++)  nxt[i]=i+1,frt[i]=i-1;
	for (int i=1;i<=m;i++){
    
    
		int u=read(),v=read();
		add_edge(u,v),add_edge(v,u);
	}
	nxt[n]=0,frt[0]=0;
	while (nxt[0]){
    
    
		int now=nxt[0],tot=0;
		del(now),l=1,r=0,q[++r]=now;
		while (l<=r){
    
    
			int cur=q[l++],saver=cur;
			tot++;
			for (int i=head[cur];i;i=e[i].nxt)  vis[e[i].to]=1;
			
			cur=0;
			while (1){
    
    
				cur=nxt[cur];
				if (!cur)  break;
				if (!vis[cur])  q[++r]=cur,del(cur);
			}
			for (int i=head[saver];i;i=e[i].nxt)  vis[e[i].to]=0;
		}
		ans[++len]=tot;
	}
	sort(ans+1,ans+len+1);
	printf("%d\n",len);
	for (int i=1;i<=len;i++)  cout<<ans[i]<<' ';
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Cherrt/article/details/118941455