点双连通分量

/*
补图:对于一个无向图G,他的完全图为K,那么K-G的所有边组成的图就是G的补图
双连通分量:无向图G的一个极大双连通子图,这个子图要分成多个子图的话必须舍弃2极其以上的点或者边
二分图:把无向图的顶点划分成两个部分,无向图的边两端的顶点分别属于这两个部分,也就是说在顶点集合内部不会有边连接
交叉染色法:判定一个图是否二分图的方法
奇圈:无向图中一条长度为奇数的回路,他的点和边不重合
*/ 
/*
一个双连通分量的某些顶点在一个奇圈中,则他的其他节点也在某个奇圈中
含有奇圈的双连通分量不是二分图,二分图不含奇圈
*/

//POJ2942 

//思维转换

//因为憎恨图中的骑士是不能被选中的,那么对憎恨图求补图,补图中的骑士是可以选中的
//因为要求每个骑士旁边有两个人,所以要求补图中的点双连通分支
//因为要求选中的人为奇数个,所以点双连通分支含有奇圈,有奇数个顶点

const int maxn = 1010 ;
const int maxm = 2* 1e6 + 10 ;

struct Edge{
    int to , next ;
}edge[maxm];
int head[maxn] , tot ;

int Low[maxn ] , DFN[maxn] , Stack[maxn] , Belong[maxn] ;
int Index , top ;
//点连通分量的个数 
int block ;
bool Instack[maxn] ;

bool can[maxn] ;
bool ok[maxn] ;
//存储双连通分量的点 
int tmp[maxn] ;
int cc ;
//染色 
int color[maxn] ;

void addedge( int u , int  v){
    edge[tot].to = v ; edge[tot].next = head[u] ; head[u] = tot ++ ;
}
//交叉染色法判断二分图 
bool dfs( int u , int col){
    //染色 
    color[u] = col ;
    for(int i = head[u] ; i!= -1 ;i= edge[i].next ){
        int v = edge[i].to ;
        //现在被判定的双连通分量的点都被打上ok标记,代表可以染色,否则就是一些不在双连通的点不管他 
        if( !ok[v ]) continue ;
        //代表已经被染色的点,他肯定是在双连通分量里面的,因为之前上一步判定为ok可以染色 
        if( color[v] != -1){
            //边的两个端点染上同一种颜色,那么不是二分图 
            if( color[v] == col )
                return false ;
            //之前已经访问的节点但是染上的是不同的颜色,不管他,即不通过他进行搜索 
            continue ;
        }
        //经过上一步判定了这个点肯定是没有染过色的,对这一个节点用不同的颜色染色 
        if( !dfs(v , !col ))
            return false ;
    }
    return true ;
} 
void Tarjan( int u , int pre){
    int v  ;
    Low[u] = DFN[u] = ++Index ;
    Stack[top ++ ] = u ;
    Instack[u] = true ;


    for( int i = head[u]  ; i!= -1 ; i=edge[i].next ){
        v = edge[i].to ;
        if( v == pre) continue ;
        //树枝边 
        if( !DFN[v]){
            Tarjan( v , u ) ;
            //之前有个回向边是Low[v]变小 
            if( Low[u] > Low[v])
                Low[u] = Low[v] ;
            //点连通分量,割点 v
            if( Low[v] >= DFN[u]){
                block ++ ;

                //点连通分量 
                int vn ;
                cc = 0 ;
                //双连通分量中的点 
                memset( ok , false , sizeof( ok )) ;
                do{
                    vn = Stack[--top ] ;
                    Belong[vn] = block ;
                    Instck[vn] = false ;
                    ok[vn] = true ;
                    tmp[ cc ++] = vn ;
                } 
                while( vn != v) ;

                ok[u] = 1 ;
                memset( color , -1 , sizeof( color )) ;
                //非二分图,交叉染色法判定图是否二分图,二分图不含有奇圈,含有奇圈的肯定不是二分图 
                if( !dfs(u , 0 )) {
                    can[u] = true ;
                    while( cc -- )
                        //该双连通分量的所有骑士都可以参加会议,割点可以属于多个双连通分量,而其他点只能属于一个连通分量 
                        can[ tmp[cc]] = true ;
                } 
            }
        }
    }
    else if( Instack[v] && Low[u] > DFN[v])
        Low[u] = DFN[v] ;
} 

void solve( int n ){
    memset( DFN , 0 ,sizeof( DFN )) ;
    memset( Instack , false , sizeof( Instack )) ;
    Index = block = top = 0 ;
    memset(can , false , sizeof( can )) ;
    for( int i = 1; i<=n ;i++)
        if( !DFN[i] )
            Tarjan( i , -1 ) ;

    //统计需要被驱逐的骑士 
    int ans = n ;
    for( int i = 1 ; i<=n; i++)
        if( can[i] )
            ans -- ;
    printf("%d\n" , ans ) ;
}

void ini() {
    tot = 0 ;
    memset( head , -1 , sizeof( head )) ;
}
int g[maxn][maxn] ;
int main(){
    int n , m ;
    int u , v ;
    while(scanf("%d%d" , & n , & m ) == 2) {
        if( n == 0 && m == 0) 
            break ;
        ini() ;
        memset( g , 0 ,sizeof( g )) ;
        while(m -- ){
            scanf("%d%d", & u , & v) ;
            //憎恨关系 
            g[u][v] = g[v][u] = 1 ;
        }
        //求补图 
        for( int i =1 ;i<=n ;i++){
            for( int j =1 ;j<=n ;j++)
                if( i != j &&g[i][j] ==0 )
                    addedge(i , j ) ;
        }
        solve( n ) ;
    }
    return 0 ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值