Cycle HDU - 5215(判断奇数环和偶数环,边双连通分量)

Ery is interested in graph theory, today he ask BrotherK a problem about it: Given you a undirected graph with N vertexes and M edges, you can select a vertex as your starting point, then you need to walk in the graph along edges. However, you can’t pass a edge more than once, even opposite direction is forbidden. At the end, you should come back to the starting point. Assume you has passed X edges, there are two questions:

Question 1: Can X be a odd number ?

Question 2: Can X be a even number ?

(note: you must walk, so X can’t be 0)
Input
The first line contains a single integer T, indicating the number of test cases.

Each test case begins with two integer N, M, indicating the number of vertexes and the number of edges. Following M lines, each line contains two integers Ui, Vi, indicating there are a edge between vertex Ui and vertex Vi.

T is about 30

1 ≤ N ≤ 100000

0 ≤ M ≤ 300000

1 ≤ Ui,Vi ≤ N

Ui will not equal to Vi

There is at most one edge between any pair of vertex.
Output
For each test, print two lines.

The first line contains “YES” or “NO” for question 1.

The second line contains “YES” or “NO” for question 2.
Sample Input
3
1 0
3 3
1 2
2 3
3 1
4 4
1 2
2 3
3 4
4 1
Sample Output
NO
NO
YES
NO
NO
YES

Hint
If you need a larger stack size,
please use #pragma comment(linker, “/STACK:102400000,102400000”) and submit your solution using C++.

题意: 判断奇环和偶环
思路:
求出边双连通分量,在每个边双连通分量内dfs求环。
因为每个连通分量之间没有桥,所以要么是个孤立点,要么每个点都在环上面。
若到达了返祖边,再算出深度差,为奇数则为奇环,否则为偶数环。
如果有大于2个的奇数环,则奇数环之间必然有至少一个公共点(或者环之间通过另一个环连接),这就可以构成偶数环。

但是本题所谓的”环“有点特殊:要求的是不能重复经过边。
这样的话,类似
5 6
1 2
2 3
3 1
1 4
4 5
5 1
这样的数据也构成本题的偶数环,
在这里插入图片描述
但不符合WIKI上环的定义。

类似的判环题目Sleepy Game CodeForces - 937D(判环)
判断奇环和0出度顶点

Permutation Cycle CodeForces - 932C(构造环)
构造一个n元环,就是数字右移,最后一个数字放在最前面。

总结一下判环:

  1. 如果是单纯判断图中有没有环,可以:dfs找返祖边,拓扑排序看能不能排完,并查集判断边上两点是否已经在一个集合内(无向图,有向图得用有权并查集),spfa判环,floyd(对链表找环问题有专门的floyd判圈算法,但是对图,我觉得只要看 f [ i ] [ i ] f[i][i] f[i][i]是否为0即可)
  2. 奇环的话只要看是不是二分图就好了,偶环不是很有底,感觉就是本题的判断方法再特判一下是不是割点就好了。
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 6e5 + 7;

int head[maxn],nex[maxn],to[maxn],tot;
int bridge[maxn],dfn[maxn],low[maxn],num;
int sum,vis[maxn],d[maxn],flag1,flag2;

void init() {
    memset(head,0,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(d,0,sizeof(d));
    memset(bridge,0,sizeof(bridge));
    flag1 = flag2 = num = 0;
    tot = 1;
}

void add(int x,int y) {
    to[++tot] = y;
    nex[tot] = head[x];
    head[x] = tot;
}

void tarjan(int x,int edge) {
    dfn[x] = low[x] = ++num;
    for(int i = head[x];i;i = nex[i]) {
        int v = to[i];
        if(!dfn[v]) {
            tarjan(v,i);
            low[x] = min(low[x],low[v]);
            if(dfn[x] < low[v]) {
                bridge[i] = bridge[i ^ 1] = 1;
            }
        }
        else if(i != (edge ^ 1)) {
            low[x] = min(low[x],dfn[v]);
        }
    }
}

void dfs(int x,int fa,int depth) {
    d[x] = depth;
    vis[x] = 1;
    for(int i = head[x];i;i = nex[i]) {
        int v = to[i];
        if(bridge[i]) continue;
        if(v == fa) continue;
        if(vis[v] == 0) {
            dfs(v,x,depth + 1);
        }
        else if(vis[v] == 1) { //返祖边
            int tmp = depth - d[v] + 1;
            if(tmp % 2 == 1) {
                sum++;
                flag1 = 1;
                if(sum >= 2) {
                    flag2 = 1;
                }
            }
            else {
                flag2 = 1;
            }
        }
    }
    vis[x] = 2;
}

int main() {
    int T;scanf("%d",&T);
    while(T--) {
        init();
        int n,m;scanf("%d%d",&n,&m);
        for(int i = 1;i <= m;i++) {
            int x,y;scanf("%d%d",&x,&y);
            add(x,y);add(y,x);
        }
        for(int i = 1;i <= n;i++) {
            if(!dfn[i]) {
                tarjan(i,0);
            }
        }
        
        for(int i = 1;i <= n;i++) {
            if(!vis[i]) {
                sum = 0;
                dfs(i,0,1);
            }
        }
        
        if(flag1) {
            printf("YES\n");
        }
        else {
            printf("NO\n");
        }
        
        if(flag2) {
            printf("YES\n");
        }
        else {
            printf("NO\n");
        }
    }
    return 0;
}

补充一个大神的解法,直接一遍染色就够了:

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn = 6e5 + 7;

int head[maxn],nex[maxn],to[maxn],tot;
int fa[maxn],color[maxn],flag[maxn];//flag代表这个点是否在奇环上
int even,odd;

void init() {
    even = odd = tot = 0;
    memset(head,0,sizeof(head));
    memset(fa,0,sizeof(fa));
    memset(color,-1,sizeof(color));
    memset(flag,0,sizeof(flag));
}

void add(int x,int y) {
    to[++tot] = y;
    nex[tot] = head[x];
    head[x] = tot;
}

void dfs(int x) {
    for(int i = head[x];i;i = nex[i]) {
        int v = to[i];
        if(v == fa[x]) continue;
        if(color[v] != -1) {
            if(color[v] != color[x]) {
                even = 1;
            }
            else {
                odd = 1;
                int now = v;
                while(!even) {
                    if(flag[now]) {
                        even = 1;
                        break;
                    }
                    flag[now]++;
                    now = fa[now];
                    if(!now || now == x)break;
                }
            }
        }
        else {
            color[v] = color[x] ^ 1;
            fa[v] = x;
            dfs(v);
        }
    }
}

int main() {
    int T;scanf("%d",&T);
    while(T--) {
        init();
        int n,m;scanf("%d%d",&n,&m);
        for(int i = 1;i <= m;i++) {
            int x,y;scanf("%d%d",&x,&y);
            add(x,y);add(y,x);
        }
        for(int i = 1;i <= n;i++) {
            if(color[i] == -1) {
                color[i] = 1;
                dfs(i);
            }
        }
        if(odd) {
            printf("YES\n");
        } else {
            printf("NO\n");
        }
        
        if(even) {
            printf("YES\n");
        } else {
            printf("NO\n");
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值