原题链接:[POI2007]办公楼biu
题意
求一个稀疏图的补图的连通块个数以及每个连通块里的点数。$N,M$非常大。
分析
考虑广搜,枚举每个点,维护一个链表记录未被访问到的点。
每次访问到一个新点,删除所有与它不相连的点即可。
具体做法可以是标记所有与它相连的点,然后在链表里删除+入队其他点。
注意这里可以用邻接表储存,并且按序号排序,可以降低查询边的复杂度。
每次查询的时候从头开始遍历链表,同时一个指针遍历这个点的出边。
由于都是递增的,一遍$O(n)$就可以处理了。
然后计算一下时间:每个点只能被入队一次,删除之后就不会再被遍历了,而且在删除之前直接跳过,所以均摊O(n)。
代码
#include <bits/stdc++.h> #define ll long long #define ull unsigned long long #define Mid ((l+r)/2) #define lson (rt<<1) #define rson (rt<<1|1) using namespace std; const int N=1000009; int read(){ char c;int num,f=1; while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0'; while(c=getchar(), isdigit(c))num=num*10+c-'0'; return f*num; } vector<int>g[N];queue<int>q; int n,m,ans[N],cnt,del[N],f[N],nxt[N],pre[N]; void add(int u,int v){g[u].push_back(v);g[v].push_back(u);} void bfs(int x){ int now=0;cnt++; while(q.size())q.pop();q.push(x); while(q.size()){ x=q.front();q.pop(); for(int i=nxt[0],k=0;i<=n;i=nxt[i]){ while(k<g[x].size()&&g[x][k]<i)k++; if(g[x][k]==i||del[i])continue; del[i]=1;nxt[pre[i]]=nxt[i];pre[nxt[i]]=pre[i]; q.push(i);now++; } } ans[cnt]=now; } int main() { n=read();m=read(); for(int i=1;i<=m;i++)add(read(),read()); for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end()); for(int i=1;i<=n;i++)nxt[i]=i+1,pre[i]=i-1;nxt[0]=1;pre[n+1]=n; for(int i=1;i<=n;i++)if(!del[i])bfs(i); printf("%d ",cnt);sort(ans+1,ans+1+cnt); for(int i=1;i<=cnt;i++)printf("%d ",ans[i]);printf(" "); return 0; }