题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1098
求补图的连通块大小。与自己没有边的和自己在一个连通块里。
用链表把所有点串起来。先给自己有边的打上标记,删掉自己,然后访问链表里的元素;没有标记的就从链表里删掉并加入栈,对每个栈里的元素重复这个操作直到栈空。一次弄出一个连通块。
这样链表里的元素越删越少,时间复杂度分析一下的话,每个点被删掉一次,其余的访问是因为有边,所以总复杂度O(n+m)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=1e5+5,M=2e6+5; int n,m,hd[N],xnt,to[M<<1],nxt[M<<1]; int pr[N],nt[N],sta[N],top,vis[N],siz[N],cnt; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return fx?ret:-ret; } void add(int x,int y) { to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt; to[++xnt]=x;nxt[xnt]=hd[y];hd[y]=xnt; } void del(int cr) { nt[pr[cr]]=nt[cr];pr[nt[cr]]=pr[cr]; } int main() { n=rdn();m=rdn(); for(int i=1,u,v;i<=m;i++) { u=rdn(); v=rdn(); add(u,v); } for(int i=1;i<=n;i++) pr[i]=i-1,nt[i]=i+1; int cr=1; while(cr<n+1) { siz[++cnt]=1; for(int i=hd[cr];i;i=nxt[i])vis[to[i]]=cr; del(cr); int nw=nt[0]; while(nw<n+1) { if(vis[nw]!=cr)sta[++top]=nw,del(nw),siz[cnt]++; nw=nt[nw]; } while(top) { int k=sta[top--]; for(int i=hd[k];i;i=nxt[i])vis[to[i]]=k; int tw=nt[0]; while(tw<n+1) { if(vis[tw]!=k)sta[++top]=tw,del(tw),siz[cnt]++; tw=nt[tw]; } } cr=nt[0]; } printf("%d ",cnt); sort(siz+1,siz+cnt+1); for(int i=1;i<=cnt;i++)printf("%d ",siz[i]); printf(" "); return 0; }