【点双连通分量+奇环判定】UVA1364 Knights of the Round Table

UVA1364 Knights of the Round Table

题意:求无向图\(G\)上不在任何一个简单奇环上的点的个数。

分析:首先知道两个定理:

  • 若双连通分量\(C\)含奇环,则\(C\)上任意一个点都位于奇环上(不一定是同一个奇环)。

  • 若双连通分量\(C\)不含奇环,则它一定是个二分图,反之也成立。

    证明\(1\):设\(C\)上的奇环为\(K\),任取两个\(K\)上的节点\(u_1,u_2\),由双连通分量性质及奇环条件可知,\(u_1,u_2\)之间必存在两条点不重复的路径,且一条有偶数个点,另一条有奇数个点。再任意取\(C\)上另外一个节点\(v\),则\(v\)\(u_1\),\(u_2\)之间有路径,其中点的个数为偶数的路径与\(u_1,u_2\)之间奇数个点的路径构成一个奇环,故\(v\)也在奇环上。

那么解题思路如下:找出\(G\)上所有双连通分量\(C_i\)→判定\(C_i\)是否为二分图→若\(C_i\)不是二分图,则将\(C_i\)上所有点标记为在奇环上→统计没有标记的点的个数,即为答案。

注意题目中给出图的边是有憎恨关系的边,我们要处理的图\(G\)是它的补图,即,把所有没有憎恨关系的节点都连上,有憎恨关系的不连。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cstdio>
#include<stack>
#define mem(a,n) memset(a,n,sizeof(a))
#define f(i,a,b) for(int i=a;i<=b;i++)
#define af(i,a,b) for(int i=a,i>=b;i--)
#define fe(u,i) for(int i=head[u];i;i=e[i].next)

using namespace std;
typedef long long LL;
const int INF = 20010509;
const int maxn = 1e3 + 100;
const int maxm = 2e4 + 100;

int dfs_clock;
int dfn[maxn], low[maxn];
int head[maxn], cnt = 0;
int n, m;
int iscut[maxn], bccno[maxn], bcc_cnt;
int inoddround[maxn];
int col[maxn];//二分图判定用
int hate[maxn][maxn];

struct Edge {
    int from, to, next;
}e[maxm];

stack<Edge> s;
//保留在当前BCC中的边
vector<int> bcc[maxn];
//记录位于bcc[i]上的所有点

void add(int from, int to, Edge eset[], int head[]) {
    cnt++;
    eset[cnt].from = from;
    eset[cnt].to = to;
    eset[cnt].next = head[from];
    head[from] = cnt;
}

bool isbinary(int u,int c) {
    col[u] = c;
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (bccno[v] != bccno[u]) continue;
      //不属于同一个双连通分量,跳过
        if (col[v] == !col[u]) continue;
        if (col[v] == col[u]) return false;
        if (col[v]==-1 && !isbinary(v, !c)) return false;
    }
    return true;
}

int dfs(int u, int fa) {
    low[u] = dfn[u] = ++dfs_clock;
    int child = 0;
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (!dfn[v]) {
            s.push(e[i]);
            child++;
            low[v] = dfs(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) {
                iscut[u] = true;
                bcc_cnt++; bcc[bcc_cnt].clear();
                for (;;) {
                    Edge x = s.top(); s.pop();
                    if (bccno[x.from] != bcc_cnt) {
                        bcc[bcc_cnt].push_back(x.from);
                        bccno[x.from] = bcc_cnt;
                    }
                    if (bccno[x.to] != bcc_cnt) {
                        bcc[bcc_cnt].push_back(x.to);
                        bccno[x.to] = bcc_cnt;
                    }
                    if (x.from == u && x.to == v) break;
                }
            }
        }
        else if (dfn[v] < dfn[u] && v != fa) {
            s.push(e[i]);
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (fa < 0 && child == 1) iscut[u] = false;
    return low[u];
}

void find_bcc() {
    dfs_clock = bcc_cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) dfs(i, -1);
    }
}

void init() {
    mem(e, 0);
    mem(head, 0);
    cnt = 0;

    mem(col, -1);
    mem(inoddround, false);
    mem(hate, 0);

    mem(dfn, 0);
    mem(low, 0);
    mem(iscut, 0);
    mem(bccno, 0);
}

int main(){
    while (cin >> n >> m) {
        if (!n && !m) break;
        init();
        for (int i = 1; i <= m; i++) {
            int u, v;
            cin >> u >> v;
            hate[u][v] = 1;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = i + 1; j <= n; j++) {
                if (!hate[i][j]) {
                    add(i, j, e, head);
                    add(j, i, e, head);
                }
            }
        }

        find_bcc();

        for (int i = 1; i <= bcc_cnt; i++) {
            mem(col, -1);
            //注意不同双连通分量之间可能会有重复的点
            //因此每次在判定二分图之前都要初始化col
            for (int j = 0; j < bcc[i].size(); j++) {
                bccno[bcc[i][j]] = i;
                //主要是为了给割点一个bcc编号,这样双连通分量才能一笔画判断二分图
            }
            if (!isbinary(bcc[i][0], 0)) {
                for (int j = 0; j < bcc[i].size(); j++) {
                    inoddround[bcc[i][j]] = true;
                }
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            if (!inoddround[i]) ans++;
        }
        cout << ans << endl;
    }
    return 0;
}
posted @ 2020-10-20 11:36  StreamAzure  阅读(136)  评论(0编辑  收藏  举报