基环图与拓扑求无向图环

基环图定义:一个树,多了一条边形成了环的图(一个树多一条边不一定形成环),即N边N点,也是只存在一个环的图

枚举删除一条边(有些题不需要枚举所有环边,比如简单的树上dp,随便找到环中的两点处理)(如果是无向图实际删除两条边a->b,b->a)然后当做树处理

无向图求环,拓扑排序或并查集

但是并查集要求环要先找到导致相连的两点,删除该边变为树,然后从其中一个点dfs一遍,用pre数组标记父节点,再从另一点通过pre找回去,才能找到环

而拓扑排序就比较简单了,与有向图tuopo差不多,加边的时候给点加度,这样的话每个点的度都是与他相连边的个数,拓扑入读的条件是改为度为1

当拓扑到与环相连的一个节点时,在环上的相连那个节点的度为3,减1变为2,不满足入队条件,于是这个环上的点就出不来了

要理解这个过程最好是手画一个图,不要复杂,三个点的环,再加两个与环相连的点即可,自己算算

给个例题:https://www.luogu.com.cn/problem/P5022

AC代码:

#include<iostream>
#include<string>
#include<stack>
#include<stdio.h>
#include<queue>
#include<string.h>
#include<map>
#include<unordered_map>
#include<vector>
#include<iomanip>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
#define INF 0x7fffffff
const int maxn = 1e5 + 100;
const int maxm = 1e6 + 100;
int read() {
    char p = getchar();
    while (p < '0' || '9' < p)p = getchar();
    int sum = 0;
    while ('0' <= p && p <= '9') sum = sum * 10 + p - '0', p = getchar();
    return sum;
}
struct E {
    int f, to, nxt;
}e[maxm];
int tot, hd[maxn];
void add(int f, int t) {
    e[++tot].to = t;
    e[tot].f = f;
    e[tot].nxt = hd[f];
    hd[f] = tot;
}//向前星存图,没学过的可以学一下,看着很牛逼,其实理解了很简单,跟邻接表差不多,但是用的空间更经凑,更快,就是把每个点的边用头插法
bool del[maxm];//判断边是否被删除了
int res[maxn];//每次dfs的结果
int cnt;
struct son {
    int id, bian;
    bool operator<(son b) {
        return id < b.id;
    }
};
vector<son>sss[maxn];//存节点的所有孩子,我用边表示孩子
int n, m;
int ans[maxn];
void init() {
    for (int i = 1; i <= n; i++) {//因为要求字典序最小,先把所有初始化为最大的
        ans[i] = INF;
    }
}
void work() {//判断是否要更新答案
    bool h = 1;
    for (int i = 1; i <= n; i++) {
        if (res[i] != ans[i]) {
            if (ans[i] < res[i]) {
                h = 0; break;
            }
            else {
                h = 1; break;
            }
        }
    }
    if (h) {
        for (int i = 1; i <= n; i++) {
            ans[i] = res[i];
        }
    }
}
void dfs(int st, int fa) {
    res[++cnt] = st;
    for (int i = 0; i < sss[st].size(); i++) {//运用贪心方法,将
        int v = sss[st][i].id;
        if (!del[sss[st][i].bian] && v != fa)
            dfs(v, st);
    }
}
bool notcircle[maxn];
int du[maxn];
void DAG() {//拓扑不要加起点,切记!!!
    queue<int>Q;
    for (int i = 1; i <= n; i++) {
        if (du[i] == 1) {
            Q.push(i);
        }
    }
    while (!Q.empty()) {
        int t = Q.front(); Q.pop();
        notcircle[t] = 1;//能出队的都不是环上的点,标记
        for (int i = hd[t]; i; i = e[i].nxt) {
            int v = e[i].to;
            if (!notcircle[v]) {
                du[v]--;
                if (du[v] == 1) {
                    Q.push(v);
                }
            }
        }
    }
}
int main() {
    //freopen("test.txt", "r", stdin);
    n = read(), m = read();
    init();
    for (int i = 1; i <= m; i++) {
        int a = read(), b = read();
        add(a, b);
        du[b]++;
        add(b, a);
        du[a]++;
        sss[a].push_back({ b ,tot - 1 });
        sss[b].push_back({ a ,tot });
    }
    for (int i = 1; i <= n; i++) {
        sort(sss[i].begin(), sss[i].end());//预先按照孩子id大小拍个序,dfs的时候直接从左向右选取即可,我之前在dfs里面去排序就超时了...
    }
    if (m == n - 1) {
        cnt = 0;
        dfs(1, 0);
        work();
    }
    else {//是基换图
        DAG();//先求环
        //枚举所有在环上的边,删除后求值,字典序最大的就是结果
        for (int i = 1; i <= tot; i += 2) {//由于是无向图,用两条有向边表示,而且存储的时候都是连着的,要就跳两次
            int u = e[i].f, v = e[i].to;
            if (!notcircle[u] && !notcircle[v]) {
                del[i] = del[i + 1] = 1;//标记这两条边被删除了
                cnt = 0;//注意初始化res的起点
                dfs(1, 0);
                work();
                del[i] = del[i + 1] = 0;//取消标记
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        printf("%d ", ans[i]);
    }
    return 0;
}

 

posted @ 2021-02-02 13:51  cono奇犽哒  阅读(267)  评论(0)    收藏  举报