基环树

众所周知,一棵树如果有\(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)\),有兴趣的可以看这篇题解

骑士

这道题之前写过,它也是一棵基环树。

具体题解可以看另一篇博客

总结

基环树的具体做法,基本上就是找出环,删去一条边再来搞,还是很好理解的。

posted @ 2022-01-12 19:01  duoluoluo  阅读(404)  评论(0)    收藏  举报