poj2942点双连通奇圈-二分图判断Knights of the Round Table

题意:在亚瑟王的圆桌骑士团中,某些骑士两两之间相互憎恨,这样一来他们开会的时候边不能相邻的坐着。即肯定存在某些人不能参加会议。假如一个骑士所有的会议都不能出席,那么他就会被驱逐。现在已知那些骑士之间相互憎恨,求最少要驱逐多少名骑士。(开会时人数必需>=3且为奇数)

题解:建图时,对互相不憎恨的两人之间连一条边。对任意一名骑士来说,他要能出席某次会议则他左右的人都不能与他互相憎恨。将每次参加会议的所有人(不一定是整个骑士团,只需人数>=3且为奇数)看做一个点双联通分量,那么每个点都至少有两个点与他相邻。即需要保证双联通分量中存在奇圈。因为只要点双连通分量中存在奇圈,那么这个分量中所有的点都可以出现在奇圈上。求奇圈时可以用到这样一个性质,一个图是二分图当且仅当图中不存在奇圈。那么我们只需判断一个图是否是二分图就可以判断此图存在奇圈,可以用交替染色。

#include <cstdio>
#include <string.h>
#define maxn 1005
#define maxm 1000005
#define min(a,b) (a<b?a:b);
struct node
{
    int to;
    int next;
} edge[maxm];
int map[maxn][maxn];//原图
int head[maxn];//头结点
int Belong[maxn];//i属于第几个双连通分量
int color[maxn];//染色序列
int n, m, cnt, ansi, Index, top, scc; //scc是双连通分量标号
int low[maxn], dfn[maxn], stack[maxn];
int temp[maxn];//临时的栈,存储双连通分支
bool instack[maxn];//是否在栈内
bool expell[maxn];// expell[i]==true表示i需要被驱逐
void init()
{
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (i != j)map[i][j] = 1;
            else map[i][j] = 0;
    for (int i = 1; i <= n; i++)
    {
        expell[i] = true;
        instack[i] = false;
        dfn[i] = low[i] = Belong[i] = 0;
        head[i] = -1;
    }
    Index = cnt = ansi = top = scc = 0;
}
void add(int a, int b)
{
    edge[ansi].to = b;
    edge[ansi].next = head[a];
    head[a] = ansi++;
}
bool odd_cycle(int u, int clr) //判断奇圈
{
    color[u] = clr;
    for (int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if (Belong[v] == scc)
        {
            if (color[v] && color[v] == color[u])
                return true;
            if (!color[v] && odd_cycle(v, -clr))
                return true;
        }
    }
    return false;
}
void tarjan(int u, int father) //求点双连通分量
{
    int v, t;
    stack[++top] = u;
    instack[u] = true;
    dfn[u] = low[u] = ++Index;
    for (int i = head[u]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if (v == father)continue;
        if (!dfn[v])
        {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) //点双连通分量满足的条件,u是割点
            {
                scc++;
                do
                {
                    t = stack[top--];
                    instack[t] = false;
                    Belong[t] = scc;
                    temp[++cnt] = t; //出栈的同时把所有的点记录在temp中,即用temp来储存双连通分支内所有的点
                }
                while (t != v); //注意不要把u出栈,因为一个割点可能属于不同的双联通分支
                temp[++cnt] = u; // 割点u属于不同的双联通分支,所以它必然也属于temp
                memset(color, 0, sizeof(color)); //所有颜色置为0
                if (cnt >= 3 && odd_cycle(u, 1)) //// 当temp中存在奇圈,那么temp中的所有人都可以留下
                {
                    while (cnt != 0)
                        expell[temp[cnt--]] = false;
                }
                else cnt = 0;
            }
        }
        else if (instack[v])
            low[u] = min(low[u], low[v]);
    }
}
int main()
{
    while (scanf("%d%d", &n, &m) != EOF && n + m)
    {
        init();
        int a, b;
        for (int i = 0; i < m; ++i)
        {
            scanf("%d%d", &a, &b);
            map[a][b] = map[b][a] = 0;
        }
        for (int i = 1; i <= n; ++i)
        {
            for (int j = 1; j <= n; ++j)
            {
                if (map[i][j])
                {
                    add(i, j);
                    add(j, i);
                }
            }
        }
        for (int i = 1; i <= n; i++)
        {
            if (!dfn[i])
                tarjan(i, -1);
        }
        int res = 0;
        for (int i = 1; i <= n; i++)
            if (expell[i])res++;
        printf("%d\n", res);
    }
}


猜你喜欢

转载自blog.csdn.net/e6894853/article/details/8841723