基环树⚓︎
对于一个连通图 G,如果其点数与边数相等,称它为一个基环树(\text{Pseudotree})。也就是说,基环树是一个包含恰好一个环的连通图。
发现环
给定一个包含 n 个点和 n 条边的无向图,图中恰有一个环。请你找出环上的所有点。
Hint
对于无向图,可以将每条边视为双向边。统计每个点的度数,环上的点度数至少为 2,而树枝上的点度数为 1。通过拓扑排序可以将所有树枝上的点剔除,剩下的即为环上的点。
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin >> n;
vector<vector<int>> g(n + 1);
vector<int> in_degree(n + 1, 0);
for (int i = 1; i <= n; ++i) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
in_degree[v]++;
g[v].push_back(u);
in_degree[u]++;
}
vector<bool> visited(n + 1, false);
queue<int> q;
for (int i = 1; i <= n; ++i) {
if (in_degree[i] == 1) { // 树枝上的点入度为 1
q.push(i);
visited[i] = true;
}
}
while (!q.empty()) {
int x = q.front();
q.pop();
for (int y : g[x]) {
if (--in_degree[y] == 1) {
q.push(y);
visited[y] = true;
}
}
}
for (int i = 1; i <= n; ++i) { // 环上的点未被访问
if (!visited[i]) { cout << i << ' '; }
}
return 0;
}
内向基环树⚓︎
基环内向树(\text{Inward Pseudotree}):有向弱连通图每个点的出度都为 1。
常常将内向基环树与拓扑排序结合使用,以求出每个点到环的距离。对于环上的点,到环的距离定义为 0;对于树枝上的点,到环的距离定义为其到环上最近点的距离。
在一些具体题目中,还会额外把环的大小作为环上节点的初始值,再向树枝部分做转移统计;这属于题目特定定义,不是“到环距离”的通用定义。
环上的点与树枝上的点可以通过统计入度来区分。环上的点入度至少为 1,而树枝上的点入度为 0。通过拓扑排序可以将所有树枝上的点剔除,剩下的即为环上的点。随后可以通过 BFS 从环上点开始,向树枝上扩展,计算每个点距离环的距离。
参加会议的最多员工数
一个公司准备组织一场会议,邀请名单上有 n 位员工。公司准备了一张圆形的桌子,可以坐下任意数目的员工。
员工编号为 0 到 n - 1 。每位员工都有一位喜欢的员工,每位员工当且仅当他被安排在喜欢员工的旁边,他才会参加会议。每位员工喜欢的员工不会是他自己。
给你一个下标从 0 开始的整数数组 favorite,其中 favorite[i] 表示第 i 位员工喜欢的员工。请你返回参加会议的最多员工数目。
#include <queue>
#include <vector>
using namespace std;
class Solution {
public:
int maximumInvitations(vector<int> &favorite) {
int n = favorite.size();
vector<int> in_degree(n);
for (int edge : favorite) { ++in_degree[edge]; }
queue<int> q;
for (int i = 0; i < n; ++i) {
if (in_degree[i] == 0) { q.push(i); }
}
vector<int> depth(n, 1);
while (!q.empty()) { // (1)!
int x = q.front();
q.pop();
int y = favorite[x];
depth[y] = max(depth[y], depth[x] + 1);
if (--in_degree[y] == 0) { q.push(y); }
}
int max_ring_size = 0, sum_chain_size = 0;
for (int i = 0; i < n; ++i) {
if (in_degree[i] == 0) { continue; }
in_degree[i] = 0; // 将基环上的点的入度标记为 0,避免重复访问
int ring_size = 1;
for (int v = favorite[i]; v != i; v = favorite[v]) { // 遍历基环上的点
in_degree[v] = 0;
++ring_size;
}
if (ring_size == 2) { // 基环大小为 2,累加两条最长链的长度
sum_chain_size += depth[i] + depth[favorite[i]];
} else { // 基环大小大于 2,只能独立成团,更新最大基环大小
max_ring_size = max(max_ring_size, ring_size);
}
}
return max(max_ring_size, sum_chain_size);
}
};
- 对于其他更复杂的性质,可以在拓扑排序过程中构建反图,反图中只包含树枝上的点,从而在后续处理中可以区分环上点与树枝上点。
外向基环树⚓︎
基环外向树(\text{Outward Pseudotree}):有向弱连通图每个点的入度都为 1。
处理方式与内向基环树类似,只需将入度改为出度即可。