Description
给定一张 nnn 个点 mmm 条边的图,你需要求出其补图的连通块个数以及各个连通块的大小。
原数据范围: 1≤n≤105,1≤m≤2×1061 \le n \le 10^5,1 \le m \le 2 \times 10^61≤n≤105,1≤m≤2×106
加强版数据范围: 1≤n,m≤5×1061 \le n,m \le 5 \times 10^61≤n,m≤5×106
Solution
补图的边数是 n2n^2n2 级别的,若我们建出整个补图则无法通过。
注意到,我们只需要连通块的信息,所以求出补图上各个连通块的生成树就足够了。该选择什么生成树呢?试试 bfs\text{bfs}bfs 树!
不难得到下面的大体思路——枚举一个 vis\text{vis}vis 为 000 的点 uuu,标记 visu=1\text{vis}_u=1visu=1 并将它放入队列。然后,我们枚举 uuu 在补图上相邻且 vis\text{vis}vis 为 000 的 vvv,再将 vvv 加入队列,以此类推。仔细分析复杂度,不难发现——若能够快速找到所有这样的点 vvv,并仅枚举它们,那么所涉及到的边就只有树边,复杂度就正确了。
结合链表的思想,维护出每个点之后第一个未被打标记的点,于是每次从 uuu 往后跳就能找到所有 vvv 了。注意到,我们还需维护删除操作,所以需要使用链表维护。
特别的,若此时 vvv 与 uuu 相邻,那么我们就忽略这个 vvv 并继续向后跳;所以对于每个 uuu,还要将它在原图上的相邻点打标记,便于在跳动过程中判断 vvv 是否合法。虽然这样并没有彻底做到 ⌈\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;
}

本文讲解了一种高效的算法,用于解决含有百万级节点和边的图中补图的连通块数量及其大小问题。通过BFS生成树策略,利用链表技巧减少查找未标记节点的复杂度,最终实现O(n+m)的时间复杂度。关键步骤包括维护补图的连通性并仅处理树边。

被折叠的 条评论
为什么被折叠?



