Author: ZYY
Date: 2025-05-09
tag: 基环树C++的基环树
一 定义
《算法进阶指南》对于基环树给出了定义:
N个点的树有N - 1条边。若在树上任意添加一条边,则会形成一个环。这种N个点N条边的连通无向图称为基环树。
有点生涩难懂对吧,我们用简单的语言对它再进行解释
即在一棵树上加一条边之后,恰好变为包含一个环的图称为基环树
同时这是在加边后仍是连通图的前提下给出的定义 反之如果加边后的图不保证连通 那么我们引申出一个新的定义
N个点、N条边的不连通无向图也可能是若干棵基环树组成的森林,简称基环树森林
二 性质
(1) 从定义出发 因为树上不能包含环 我们发现基环树其实并不是一棵树
(2) 内向树与外向树
我们将每个点仅有一条出边的有向图称为内向树
我们将每个点仅有一条入边的有向图称为外向树
注: 我们将内向树和外向树都统一归纳为基环树。
这里为大家提供一幅图 对比了普通基环树,内向树,外向树

三 处理
基环树的结构虽然简单仅仅又一棵树再加一条边组成 但是处理起来还是有一定难度的 处理基环树的关键就在于如何找到环 接着我们就可以将环作为基环树的根节点 最后将除了环以外的树处理后再带着环一起计算
(1)对于无向图
1> 拓扑排序找环并记录环上的点
cpp
// 利用拓扑排序找环并记录环上所有点
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int n, m;
int h[N], e[M], ne[M], idx; // 邻接表建图
int d[N]; // 记录入度
int ans[N]; // 记录环上点的编号
int cnt; // 记录环上点的个数
inline void add(int a, int b) { // 建图
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
queue<int> q;
inline void topsort() {
for (int i = 1; i <= n; i++) { // 初始化入度
if (!d[i]) {
q.push(i);
}
}
while (!q.empty()) {
int t = q.front();
q.pop();
ans[cnt++] = t; // 记录环上点的编号
for (int j = h[t]; j != -1; j = ne[j]) {
int y = e[j];
d[y]--;
if (d[y] == 0) { // 如果入度为 0,加入队列
q.push(y);
}
}
}
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i++) {
int a, b;
cin >> a >> b;
add(a, b); add(b, a);
d[a]++, d[b]++;
}
topsort();
for (int i = 0; i < cnt; i++) {
cout << ans[i] << " "; // 输出环上点的编号
}
}2> Tarjan直接找环并标记环上的点
cpp
// DFS找环并记录环上的点
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], t, cnt; // dfn指的是dfs的顺序,low是能到达的最小的dfn
stack<int> stk;
bool in_stk[N]; // 记录栈中是否存在
vector<int> lex; // 记录环上的点
inline void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
inline void Tarjan(int u, int fa) {
dfn[u] = low[u] = ++t; // 初始化dfn和low
stk.push(u);
in_stk[u] = true;
for(int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if(v == fa) continue; // 如果是父节点就跳过
if(!dfn[v]) { // 如果没有访问过
Tarjan(v, u);
low[u] = min(low[u], low[v]);
} else if(in_stk[v]) {
low[u] = min(low[u], dfn[v]);
if(lex.empty()) {
stack<int> lnx = stk; // 复制栈
while(!lnx.empty() && lnx.top() != v) {
lex.push_back(lnx.top());
lnx.pop();
}
lex.push_back(v);
}
}
}
if(dfn[u] == low[u]) {
while(!stk.empty()) {
int v = stk.top();
stk.pop();
in_stk[v] = false;
if(v == u) break;
}
}
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
Tarjan(1, -1);
for(int x : lex) {
cout << x << " ";
}
cout << endl;
return 0;
}对于有向图
拓扑排序只针对无向图 所以我们只能用Tarjan记录环
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], t;
stack<int> stk;
bool in_stk[N];
vector<int> lex;
inline void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
inline void Tarjan(int u) {
dfn[u] = low[u] = ++t;
stk.push(u);
in_stk[u] = true;
for(int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if(!dfn[v]) {
Tarjan(v);
low[u] = min(low[u], low[v]);
} else if(in_stk[v]) {
low[u] = min(low[u], dfn[v]);
if(lex.empty()) {
stack<int> lnx = stk;
while(!lnx.empty() && lnx.top() != v) {
lex.push_back(lnx.top());
lnx.pop();
}
lex.push_back(v);
}
}
}
if(dfn[u] == low[u]) {
while(!stk.empty()) {
int v = stk.top();
stk.pop();
in_stk[v] = false;
if(v == u) break;
}
}
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
add(a, b);
}
for(int i = 1; i <= n; i++) {
if(!dfn[i]) Tarjan(i);
}
for(int x : lex) {
cout << x << " ";
}
cout << endl;
return 0;
}四 练习
Jouier
双倍经验
如果你想更强
如果你想变得更强 就多刷题吧 基环树题单
五,补充知识
在做岛屿时 我们会发现要求变化中的区间最值 我们这里补充一下单调队列这个知识点
从名字出发,单调是指保证区间内是单调递增/单调递减队列是指具有队列的性质 即只能对队头/队尾进行操作

单调队列解决的主要问题是规定区间长度 求区间最值 我们一般选择在每一次更新区间时直接更换最大/最小值 这样可以保证时间复杂度最低
language
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N], q[N], n, k;
void get_min() {
int hh = 0, tt = -1;
for(int i = 0; i < n; i++) {
while(hh <= tt && i - k + 1 > q[hh]) hh++;
while(hh <= tt && a[q[tt]] >= a[i]) tt--;
q[++tt] = i;
if(i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
}
void get_max() {
int hh = 0, tt = -1;
for(int i = 0; i < n; i++) {
while(hh <= tt && i - k + 1 > q[hh]) hh++;
while(hh <= tt && a[q[tt]] <= a[i]) tt--;
q[++tt] = i;
if(i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
}
int main() {
scanf("%d%d", &n, &k);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
get_min();
get_max();
return 0;
}