基环图与拓扑求无向图环
基环图定义:一个树,多了一条边形成了环的图(一个树多一条边不一定形成环),即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; }