// 题目来源:POJ 2942 ( Central Europe 2005 ) // 题目模型:给定一个无向图G,求图中哪些点不能够在任何奇圈之内(奇圈即点数为奇数的圈) // 解题方法:对图求块,然后在各个块内二分染色判断 // 特别注意:求块的时候在栈中要压边不能压点 #include <cstdio> #include <string> using namespace std; bool map[1002][1002], ok; int next[2000002], p[2000002], g[2000002], h[1002], stack[2000002], dfn[1002], low[1002], c[1002][1002], code[1002]; int t, sum, index, top, cnt, n, m; void link( int aa, int bb ); // 邻接表构建 void tarjan( int i, int num ); // 求点双连通分量 void color( int i ); // 在双连通分量内染色进行二分图判定 void make( ); // 根据得到的信息进行标记出不可行的点 int main( ) { freopen( "input.txt", "r", stdin ); freopen( "output.txt", "w", stdout ); int aa, bb; scanf( "%d%d", &n, &m ); while( n != 0 ) { t = 1; cnt = 0; sum = 0; memset( next, 0, sizeof( next ) ); memset( code, 0, sizeof( code ) ); memset( map, 0, sizeof( map ) ); memset( dfn, 0, sizeof( dfn ) ); memset( low, 0, sizeof( low ) ); memset( h, 0, sizeof( h ) ); memset( p, 0, sizeof( p ) ); memset( c, 0, sizeof( c ) ); // 数组初始化 for( int i = 1; i <= m; i++ ) { scanf( "%d%d", &aa, &bb ); map[aa][bb] = map[bb][aa] = 1; // 不可行边标记 } for( int i = 1; i <= n; i++ ) for( int j = i+1; j <= n; j++ ) if( !map[i][j] ) link( i, j ); // 根据可行边构图 for( int i = 1; i <= n; i++ ) if( dfn[i] == 0 ) // 点未访问过 { index = 0; // 时间戳初始化 tarjan( i, 0 ); // 带边进行tarjan过程,求出所有的点双连通分量,并染色判定打标记 } for( int i = 1; i <= n; i++ ) if( code[i] == 0 ) sum++; // 该点不在任何奇圈之内 printf( "%d\n", sum ); scanf( "%d%d", &n, &m ); } return 0; } void link( int aa, int bb ) { next[++t] = h[aa]; h[aa] = t; g[t] = bb; next[++t] = h[bb]; h[bb] = t; g[t] = aa; } void tarjan( int i, int num ) { int j, e; // j取点,e取边 dfn[i] = low[i] = ++index; for( int k = h[i]; k; k = next[k] ) { j = g[k]; if( (num^k) == 1 || dfn[j] > dfn[i] ) continue; // 是指向父亲的边则放弃 stack[++top] = k; // 当前边入栈 if( !dfn[j] ) // 若指向节点未被访问 { tarjan( j, k ); // 带边进行递归 if( low[j] < low[i] ) low[i] = low[j]; // low值传递 if( dfn[i] <= low[j] ) // 判断当前是否产生了一个块 { cnt++; // 块的数量增加 do { e = stack[top--]; p[e] = p[e^1] = cnt; // 将块内的边弹栈并打上块编号 } while( e != k ); c[cnt][j] = 1; // j节点打上颜色标记 ok = 0; // 是否找到奇圈初始化 color( j ); // 从j节点开始进行颜色标记 if( ok ) make( ); // 若找到奇圈则给块内节点标记可行 } } else if( dfn[j] < low[i] ) low[i] = dfn[j]; // 修改low值 } } void color( int i ) { int j; for( int k = h[i]; k; k = next[k] ) { if( p[k] != cnt ) continue; // 若当前边不在当前块内则忽视 j = g[k]; if( c[cnt][j] == 0 ) // 若目标节点未被染色 { c[cnt][j] = 3 - c[cnt][i]; // 将其染上不同的颜色 color( j ); // 继续递归标记 } else if( c[cnt][j] == c[cnt][i] ) // 出现相邻节点同色 ok = 1; // 奇圈寻找成功 } } void make( ) { for( int i = 1; i <= n; i++ ) if( c[cnt][i] != 0 ) code[i] = 1; // 被染色过则标记可行 }