秒懂算法 | 基环树

简介: 图论是一个“巨大”的专题,有大量的知识点,有众多广为人知的问题,有复杂的应用场景。图论算法常常建立在复杂的数据结构之上。

01、基环树

基环树是只有一个环的连通图,它有n个点和n条边。基环树不是一棵树,而是一棵“伪树”。它的特征是图中有且只有一个连通的环。下面分别讨论无向图和有向图两种情况下的基环树。

(1) 无向图上的基环树。在一棵基于无向图的无根树上加一条边,形成基环树。去掉环上任意一条边,基环树变成一棵真正的树。

(2) 有向图上的基环树。一个有向无环图(DAG),如果在图中加一条边能形成一个自连通的环,则形成一棵基环树。把这个环看作一个整体,根据它与环外点的关系,把基环树分成两种:内向树,环外的点只能进入环内;

外向树,环外的点无法进入环内。

图1(1)~图1(3)是基环树的3种形态,把环缩成一个“虚点”后得到图1(4)~图1(6)。请注意,缩成虚点后的无向图1(4)是一棵树,但是缩成虚点后的有向图1(5)和图1(6)不一定是真正的树,如图1(5)就不是一棵树。

640.png


■ 图1 基环树的三种形态和缩点图


由基环树的特征可知,与基环树有关的题目,首先是找到唯一的环,然后把这个环当作“虚点”。

由前面的无向图的连通性和有向图的连通性可知,基环树的找环问题是“图的连通性”的一个简化问题。

对于无向图,用拓扑排序的BFS可以找出环,操作结束后,度大于1的点就是环上的点。具体做法:①计算所有点的度;②把所有度为1的点入队;③从队列弹出度为1的点,把它所连的边去掉,并将边所连的邻居点的度减1,若这个邻居的度变为1,入队;④继续执行步骤③直到队列为空。操作结束后,统计所有点,度数大于1的点即为环上的点。注意,这种无向图找环的方法只适用于只有一个环的基环树。

如果只要找环上的一个点,用DFS可以方便地找到。如果有一个点v第2次被访问到,那么就存在环,且v是环上的一个点。这个方法可用于有向图和无向图。下面的例题用到了这个原理。

640.png


把x讨厌的人y设为x的父节点,形成从y指向x的有向边。本题每个点的入度为1,生成的图中包括很多独立的连通块,每个连通块肯定是一棵基环树,而且形状是一棵外向树。这些基环树形成了基环树森林。在每棵基环树上找到环,断开这个环后,这棵基环树变成了一棵真正的树,就可以套用洛谷P1352的做法了。

本题属于“基环树+树上DP”,下面给出代码。其中的DP代码和洛谷P1352一样,这里不再解释。基环树部分有以下两处。

(1) 找基环树环上一个点。用check_c()函数实现,它是一个DFS,如果发现某个点被第2次访问,它就是环上的一个点,用mark记录这个点。

(2) 分别断开mark和mark的父节点,形成两棵树,在这两棵树上分别做DP,取最大值。


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 100;
vector<int> G[N];
int father[N],val[N],mark;
bool vis[N];
ll dp[N][2];
void addedge(int from,int to){
    G[from].push_back(to);          //用邻接表建树
    father[to] = from;              //父子关系
}
void dfs(int u){                    //和洛谷P1352 几乎一样
    dp[u][0] = 0;                   //赋初值:不参加
    dp[u][1] = val[u];              //赋初值:参加
    vis[u] = true;
    for(int v : G[u]){              //遍历u的邻居v
        if(v == mark)  continue;   
        dfs(v);
        dp[u][1] += dp[v][0];       //父结点选择,子结点不选
        dp[u][0] += max(dp[v][0],dp[v][1]);  //父结点不选,子结点可选可不选
    }
}
int check_c(ll u){                  //在基环树中找环上一个点
    vis[u] = true;
    int f = father[u];
    if(vis[f]) return f;            //第2次访问到,是环上一个点
    else       check_c(f);          //继续向父结点方向找
}
ll solve(int u){                    //检查一棵基环树
    ll res = 0;
    mark = check_c(u);              //mark是基环树的环上一个点 
    dfs(mark);                      //做一次dfs
    res = max(res,dp[mark][0]);     //mark不参加
    mark = father[mark];
    dfs(mark);                      //mark的父结点不参加,再做一次dfs
    res = max(res,dp[mark][0]);
    return res;
}
int main(){
    int n; scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        int d;   scanf("%d%d",&val[i],&d);    addedge(d,i);
    }
    ll ans = 0;
    for(int i = 1;i <= n;i++)
        if(!vis[i]) ans += solve(i);  //逐个检查每棵基环树
    printf("%lld\n",ans);
    return 0;
}
目录
相关文章
|
4月前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
115 1
|
1月前
|
算法
树的遍历算法有哪些?
不同的遍历算法适用于不同的应用场景。深度优先搜索常用于搜索、路径查找等问题;广度优先搜索则在图的最短路径、层次相关的问题中较为常用;而二叉搜索树的遍历在数据排序、查找等方面有重要应用。
39 2
|
6月前
|
存储 算法 Java
Java中,树与图的算法涉及二叉树的前序、中序、后序遍历以及DFS和BFS搜索。
【6月更文挑战第21天】Java中,树与图的算法涉及二叉树的前序、中序、后序遍历以及DFS和BFS搜索。二叉树遍历通过访问根、左、右子节点实现。DFS采用递归遍历图的节点,而BFS利用队列按层次访问。以下是简化的代码片段:[Java代码略]
51 4
|
6月前
|
存储 算法 Linux
【数据结构和算法】---二叉树(1)--树概念及结构
【数据结构和算法】---二叉树(1)--树概念及结构
58 0
|
2月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
32 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
3月前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
在数据密集型应用中,高效搜索算法至关重要。Trie树(前缀树/字典树)通过优化字符串处理和搜索效率成为理想选择。本文通过Python实战演示Trie树构建与应用,显著提升搜索性能。Trie树利用公共前缀减少查询时间,支持快速插入、删除和搜索。以下为简单示例代码,展示如何构建及使用Trie树进行搜索与前缀匹配,适用于自动补全、拼写检查等场景,助力提升应用性能与用户体验。
71 2
|
2月前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
34 0
|
2月前
|
存储 算法 Java
数据结构和算法--分段树
数据结构和算法--分段树
25 0
|
5月前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
【7月更文挑战第19天】Trie树,又称前缀树,是优化字符串搜索的高效数据结构。通过利用公共前缀,Trie树能快速插入、删除和查找字符串。
129 2