点双连通分量

来源:互联网 发布:网络医疗美容咨询 编辑:程序博客网 时间:2024/04/30 02:30
/*补图:对于一个无向图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 ;}