洛谷 [POI2007]BIU-Offices 解题报告

[POI2007]BIU-Offices

题意

给定(n(le 100000))个点(m(le 2000000))条边的无向图(G),求这个图(G)补图的连通块个数。


一开始想了半天各种啥啥啥优化补图连边。

但复杂度没算好,最开始( t{set})的想法是可以通过此题的。

使用链表+队列可以做到(O(n+m))的复杂度

具体流程如下:

  1. 将所有的点加入链表
  2. 从链表中随便拿出一个点加入队列,如果链表为空,结束
  3. 遍历队列
    • 对于当前点,把( t{Ta})的连接的边打标记。
    • 遍历链表,取出没有打标记的点从链表中删去并加入队列。
    • 取消标记。
  4. (3)中进入队列的点统计为一个连通块

考虑这样的复杂度为什么是(O(n+m))

每条边两边的点会被打一次标记并取消一次标记,并最多一次作为遍历链表时没有被删去的点,这里是(O(m))的。

每个点最多会从链表中删去一次,这里是(O(n))的。


Code:

#include <cstdio>
#include <algorithm>
const int N=1e5+10;
const int M=2e6+10;
int head[N],to[M<<1],Next[M<<1],cnt;
void add(int u,int v)
{
    to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int n,m,pre[N],suc[N],q[N],l,r,ans[N],col[N],tot;
int main()
{
    scanf("%d%d",&n,&m);
    for(int u,v,i=1;i<=m;i++)
        scanf("%d%d",&u,&v),add(u,v),add(v,u);
    for(int i=1;i<=n;i++)
        pre[i]=i-1,suc[i]=i+1;
    suc[0]=1,suc[n]=0;
    while(suc[0])
    {
        l=1,r=0;
        q[++r]=suc[0];
        suc[0]=suc[suc[0]];
        pre[suc[q[r]]]=0;
        while(l<=r)
        {
            int now=q[l++];
            for(int i=head[now];i;i=Next[i])
                col[to[i]]=1;
            int cur=suc[0];
            while(cur)
            {
                if(!col[cur])
                {
                    q[++r]=cur;
                    pre[suc[cur]]=pre[cur];
                    suc[pre[cur]]=suc[cur];
                }
                cur=suc[cur];
            }
            for(int i=head[now];i;i=Next[i])
                col[to[i]]=0;
        }
        ans[++tot]=l-1;
    }
    std::sort(ans+1,ans+1+tot);
    printf("%d
",tot);
    for(int i=1;i<=tot;i++) printf("%d ",ans[i]);
    return 0;
}

2018.11.8

原文地址:https://www.cnblogs.com/butterflydew/p/9929606.html