【洛谷】 1197 [JSOI2008] 星球大战

比较好的并查集!


思路:


将每个星球看成一个点,以太隧道为边,建成一个图的模型!


O(q)每一次询问对于一个点,加上O(m)枚举删除的每一条边,O(n)暴力 Tarjan(),时间复杂度为 O(nq + m);

稳 T 掉,懒得试多少分 ……


想了一下,求连通块的方法除了 Tarjan(),还有并查集 。但是并查集也不支持快速删除啊 ……

但是并查集可以加点判断连通块 …… 对!如果从最后一次删除开始添加每一个点,这样只需要枚举与该点连接的边,然后判断这条边连接的另一个点是否在这个图中,注意,有些点可能还没有加进来,所以合并并查集的条件就是必须另一点需要被添加在图中。

对于幸免,也就是没有被删掉的点,他们一开始就是在这个图中的,所以需要预处理出在已经在图中的点,先将他们加入重构的图中,然后再反着询问顺序加入被删除的点。


我其实一开始有一个疑问:

如果将这某个点加进去,那么它相邻的每一条边也就存在了,然而又需要开一个边的集合存这个点所连接的边,因为必须要保证每一条边都在才能保证不漏掉它的贡献 ……

被卡住了,怎么写?


其实这个疑问是多余的,因为如果某一条边都会被枚举到,被枚举到时,如果这时它能使连通块个数减一,那么一定会产生贡献,否则不会产生任何贡献 。枚举并不会漏掉任何一条边,并且任何一条边的贡献也不会出差错 。

所以这个疑问是多余的 。


代码:


#include <bits/stdc++.h>

const  int  N = 400000 + 5 ; 

int  head [ N ] , nxt [ N ] , to [ N ] , cn ;
int  tim [ N ] , use [ N ] , num [ N ] , fa [ N ] ;
int  n , m , x , y , k , ans ;

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

void  create ( int , int ) ;
void  init ( ) ;
void  work ( ) ;
void  print ( ) ;

int  main ( ) {
    
    scanf ( "%d%d" , & n , & m ) ;
    for ( int  i = 1 ; i <= m ; i ++ ) {
        scanf ( "%d%d" , & x , & y ) ;
        create ( x , y ) ;
        create ( y , x ) ;
    }
    scanf ( "%d" , & k ) ;
    for ( int  i = 1 ; i <= k ; i ++ ) {
        scanf ( "%d" , & tim [ i ] ) ;
        use [ tim [ i ] ] = true ;
    }
    init ( ) ;
    work ( ) ;
    print ( ) ;
    return  0 ;
}

void  create ( int  u , int  v ) {
    cn ++ ;
    to [ cn ] = v ;
    nxt [ cn ] = head [ u ] ;
    head [ u ] = cn ;
}

void  init ( ) {
    for ( int  i = 0 ; i < n ; i ++ )
        fa [ i ] = i ;
    for ( int  i = 0 ; i < n ; i ++ ) {
        if ( use [ i ] )  continue ;
        ans ++ ;
        for ( int  j = head [ i ] ; j ; j = nxt [ j ] ) {
            int  v = to [ j ] ;
            if ( use [ v ] )  continue ;
            int  tmp1 = find ( i ) , tmp2 = find ( v ) ; 
            if ( tmp1 != tmp2 ) {
                fa [ tmp2 ] = tmp1 ;
                ans -- ;
            }
        }
    }
    num [ k + 1 ] = ans ;
}

void  work ( ) {
    for ( int  i = k ; i >= 1 ; i -- ) {
        int  u = tim [ i ] ;
        ans ++ ;
        use [ u ] = false ;
        for ( int  j = head [ u ] ; j ; j = nxt [ j ] ) {
            int  v = to [ j ] ;
            if ( use [ v ] )  continue ;
            int  tmp1 = find ( u ) , tmp2 = find ( v ) ; 
            if ( tmp1 != tmp2 ) {
                fa [ tmp2 ] = tmp1 ;
                ans -- ;
            }
        }
        num [ i ] = ans ;
    }
}

void  print ( ) {
    for ( int  i = 1 ; i <= k + 1 ; i ++ )
        printf ( "%d\n" , num [ i ] ) ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值