UVa1364/LA3523 Knights of the Round Table

本文介绍了如何解决一个关于骑士互相憎恨的圆桌会议问题,通过构建图并分析双连通分量来确定哪些骑士不可能参加会议。主要算法涉及检测非二分图的双连通分量,以及检查是否存在奇数长度的环。给出了C语言的AC代码实现。
摘要由CSDN通过智能技术生成

UVa1364/LA3523 Knights of the Round Table

题目链接

   本题是2005年ICPC欧洲区域赛中欧赛区A

题意

   有n个骑士经常举行圆桌会议,商讨大事。每次圆桌会议至少应有3个骑士参加,且相互憎恨的骑士不能坐在圆桌旁的相邻位置。如果发生意见分歧,则需要举手表决,因此参加会议的骑士数目必须是奇数,以防止赞同和反对票一样多。知道哪些骑士相互憎恨之后,你的任务是统计有多少个骑士不可能参加任何一个会议。

分析

   将不相互憎恨的骑士连一条无向边建图,如果对一个结点(骑士)能找到含奇数个结点且结点不重复的圈,则此骑士至少可以参加一个会议。​因此需要先找出所有双连通分量,二分图不存在长度为奇数的环,所以只需要关注那些不是二分图的双连通分量。更进一步,如果一个双连通分量不是二分图,它至少含有一个长度为奇数的环。
   一个双连通分量含有一个长度为奇数的环 C C C,分析一下此分量中不在环C上的某个结点 v v v
含长度为奇数的环的双连通分量
   根据双连通性, C C C中应该存在两个不同的结点 u 1 u_1 u1 u 2 u_2 u2,使得从 v v v出发有两条不相交路径(除起点外无公共结点),分别到 u 1 u_1 u1 u 2 u_2 u2。由于在 C C C中,从 u 1 u_1 u1 u 2 u_2 u2的两条路的长度一奇一偶,总能构造出一个经过 v v v的长度为奇数的环。
   至此,主算法清晰了:对于每个连通分量的每个双连通分量 B B B,若它不是二分图,给 B B B中所有结点标记为“在长度为奇数的环上”,最后遍历每个结点,统计出无标记的结点数就是答案。

AC 代码

#include <iostream>
#include <cstring>
using namespace std;

#define N 1010
int s[N*N][2], bcc[N][N], g[N][N], c[N], t[N], pre[N], bn[N], color[N], clk, cc, m, n, p; bool off[N][N], ex[N];

int dfs(int u, int fa = -1) {
    int low = pre[u] = ++clk;
    for (int i=0, v; i<c[u]; ++i) if (!pre[v = g[u][i]]) {
        s[p][0] = u; s[p++][1] = v;
        int lowv = dfs(v, u); low = min(low, lowv);
        if (lowv >= pre[u]) {
            t[++cc] = 0;
            while (true) {
                int x = s[--p][0], y = s[p][1];
                if (bn[x] != cc) bcc[cc][t[cc]++] = x, bn[x] = cc;
                if (bn[y] != cc) bcc[cc][t[cc]++] = y, bn[y] = cc;
                if (x == u && y == v) break;
            }
        }
    } else if (pre[v] < pre[u] && v != fa) {
        s[p][0] = u; s[p++][1] = v; low = min(low, pre[v]);
    }
    return low;
}

bool bipartite(int u, int b) {
    for (int i=0, v; i<c[u]; ++i) if (bn[v = g[u][i]] == b) {
        if (color[v] == color[u]) return false;
        if (!color[v]) {
            color[v] = 3-color[u];
            if (!bipartite(v, b)) return false;
        }
    }
    return true;
}

int solve() {
    memset(off, 0, sizeof(off)); memset(c, p = 0, sizeof(c)); memset(ex, 1, sizeof(ex));
    memset(pre, clk = 0, sizeof(pre)); memset(bn, cc = 0, sizeof(bn));
    while (m--) {
        int u, v; cin >> u >> v;
        off[u][v] = off[v][u] = 1;
    }
    for (int u=1; u<=n; ++u) for (int v=u+1; v<=n; ++v) if (!off[u][v]) g[u][c[u]++] = v, g[v][c[v]++] = u;
    for (int u=1; u<=n; ++u) if (!pre[u]) dfs(u);
    for (int i=1; i<=cc; ++i) {
        for (int j=0; j<t[i]; ++j) bn[bcc[i][j]] = i, color[bcc[i][j]] = 0;
        int u = bcc[i][0]; color[u] = 1;
        if (!bipartite(u, i)) for (int j=0; j<t[i]; ++j) ex[bcc[i][j]] = 0;
    }
    int ans = 0;
    for (int i=1; i<=n; ++i) if (ex[i]) ++ans;
    return ans;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n >> m && n) cout << solve() << endl;
    return 0;
}
  • 23
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值