// 题目来源: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; // 被染色过则标记可行
}
【代码】POJ 2942
最新推荐文章于 2020-06-16 23:42:29 发布