/*
补图:对于一个无向图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 ;
}
点双连通分量
最新推荐文章于 2023-08-15 10:01:19 发布