基环树
众所周知,一棵树如果有\(N\)个节点,那么这棵树有\(N - 1\)条边,而基环树就是有\(N\)个节点,\(N\)条边,相当于在一棵树上多连一条边形成一个环。
所以对于基环树,我们在树上可以用的算法是不能直接套进基环树里面用的。
那么如何解决呢?
把这个基环树删掉一条边变成树就行了!
基本上所有题目都是这样子的qwq
既然要删除一条边,首先肯定要找到基环树上的环。
找环
先从任意节点出发\(DFS\),记录每个节点的父亲节点,当发现\(x\)的子节点\(y\)已经有了一个父亲节点,很显然\(x\)与\(y\)的边是环上的其中一边,并且我们把\(x\)一直往父亲节点向上跳,跳到\(y\)为止,这其中经过的节点都是环上的节点。
代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1010;
int n, m, head[N], nex[N], ver[N], cnt[N], fa[N], flag, tot;
inline void add (int x, int y) {
ver[++ tot] = y;
nex[tot] = head[x];
head[x] = tot;
}
void dfs (int x, int f) { //x表示当前节点,f表示x的上一个节点
if (flag) return; //已经找到环
fa[x] = f;
for (int i = head[x]; i; i = nex[i]) {
if (flag) return; //已经找到环
int y = ver[i];
if (y == f) continue;
if (fa[y]) { //y已经有父亲节点,且这个节点肯定不是x
while (x != y) {
cnt[x] = 1;
x = fa[x];
}
cnt[y] = 1;
flag = 1;
return;
} else
dfs(y, x);
}
}
int main () {
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);
}
dfs(1, 1);
for (int i = 1; i <= n; i ++)
if (cnt[i])
printf("%d ", i);
return 0;
}
既然已经找到了环,那么每次就删掉环上的一条边,在删完边后的树中套上树的算法就行了。
试题
题解
旅行
当\(m = n - 1\)时,显然从\(1\)出发每次先走编号最小的节点即可。
当\(m = n\)时,显然就是基环树了。
那么就每次删掉环上一条边,在剩下的图中用\(m = n - 1\)的方法跑,最后在输出字典序最小的答案即可。
时间复杂度\(O(n^2)\)
代码:
\[\]
\[\]
\[\]
\[\]
\[\]
\[\]
\[\]
\[\]
\[\]
因为我太懒了,写了另外一种算法,所以这题没有代码qwq
此题还有数据加强版:旅行(数据加强版)
我用了一个贪心的算法,时间复杂度\(O(nlogn)\),有兴趣的可以看这篇题解。
骑士
这道题之前写过,它也是一棵基环树。
具体题解可以看另一篇博客。
总结
基环树的具体做法,基本上就是找出环,删去一条边再来搞,还是很好理解的。

浙公网安备 33010602011771号