【代码】POJ 2942

// 题目来源: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;  // 被染色过则标记可行
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值