[BZOJ1098]-[POI2007]办公楼biu-反图遍历

说在前面

me记得me打的唯一一场CF里面有这道题,当时me写的数据分治被hack掉了(绝望)
然后交到这里又TLE掉了emmmm…
今天终于把坑填了


题目

BZOJ1098传送门

题目大意

现在给出一张 n n 个点的图,其中有 m 条边没有(也就是说,没有给出的边是存在的,给出的边是不存在的)
现在询问该图的反图的连通块个数,以及每个连通块的大小
范围: n105 n ≤ 10 5 m106 m ≤ 10 6

输入输出格式

输入格式:
第一行两个整数 n,m n , m ,含义如题
接下来 m m 行,每行两个整数描述一条不存在的边

输出格式:
先输出一行一个整数 k,表示反图连通块个数
然后再输出一行 k k 个整数,表示每个块的大小,从小到大依次输出


解法

我们回顾一下正图的遍历,因为边很少,所以对于每个点,可以直接枚举它连的边进行BFS/DFS,复杂度是O(n+m)

但是对于反图,我们不能再去枚举有哪些边,而要去枚举没有哪些边,然后继续遍历和它相临的点
因为要保证复杂度,对于每个点我们只能遍历一次,所以我们需要记录下哪些点还没有被遍历,这个可以用一个set/链表来维护

对于BFS的写法:
每次查找与当前点 u u 相连的点时,先把所有与 u 无边的点 ban 掉,然后枚举还没有被遍历的点 v v ,如果 v 没被 ban 掉,说明 v v u 相连,然后把 v v 加进队列
这样的话,每个点每条边都只会被访问一次,复杂度就ok了

对于DFS的写法:
每次都从未遍历集合取出一个点 v,判断是否与当前点有边,如果有,把 v v 移出集合,然后进入 v 继续dfs


下面是代码

me用的BFS实现

#include <set>
#include <cstdio>
#include <cstring>
#include <functional>
#include <algorithm>
using namespace std ;

int N , M , head[100005] , tp , fa[100005] ;
struct Path{
    int pre , to ;
}p[4000005] ;

void In( int t1 , int t2 ){
    p[++tp] = ( Path ){ head[t1] , t2 } ; head[t1] = tp ;
    p[++tp] = ( Path ){ head[t2] , t1 } ; head[t2] = tp ;
}

int find( int x ){
    if( fa[x] == x ) return x ;
    return fa[x] = find( fa[x] ) ;
}

int cnt[100005] , tot ;
bool vis[100005] ;
set<int> s0 , s1 ;
void solve(){
    for( int i = 1 ; i <= N ; i ++ )
        fa[i] = i , s1.insert( i ) ;

    while( !s1.empty() ){
        int u , f ;
        if( !s0.empty() ) u = *s0.begin() , s0.erase( s0.begin() ) ;
        else u = *s1.begin() , s1.erase( s1.begin() ) ;
        f = find( u ) ;

        for( int i = head[u] ; i ; i = p[i].pre )
            vis[ p[i].to ] = true ;
        for( set<int>::iterator it = s1.begin() ; it != s1.end() ; ){
            int v = (*it) ; ++ it ;
            if( vis[v] ) continue ;
            fa[ find( v ) ] = f , s1.erase( v ) , s0.insert( v ) ;
        }
        for( int i = head[u] ; i ; i = p[i].pre )
            vis[ p[i].to ] = false ;
    }
    for( int i = 1 ; i <= N ; i ++ ) cnt[ find( i ) ] ++ ;
    sort( cnt + 1 , cnt + N + 1 , greater<int>() ) ;
    for( int i = 1 ; !tot ; i ++ ) if( !cnt[i] ) tot = i - 1 ;

    printf( "%d\n" , tot ) ;
    for( int i = tot ; i ; i -- ) printf( "%d " , cnt[i] ) ;
}

int main(){
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 , u , v ; i <= M ; i ++ ){
        scanf( "%d%d" , &u , &v ) ;
        In( u , v ) ;
    } solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值