基环树
基环树入门
基环树是只有一个环的连通图,它有 nn 个点和 nn 条边。基环树不是一棵树,而是一棵“伪树”。它的特征是图中有且只有一个连通的环。下面分别讨论无向图和有向图两种情况下的基环树。
(1)无向图上的基环树。在一棵基于无向图的无根树上加一条边,形成基环树。去掉环上任意一条边,基环树变成一棵真正的树。
(2) 有向图上的基环树。一个有向无环图(DAG) , 如果在图中加一条边能形成一个自连通的环,则形成一棵基环树。把这个环看作一个整体,根据它与环外点的关系,把基环树分成两种:内向树,环外的点只能进人环内;外向树,环外的点无法进入环内。
图示:基环树的三种形态和缩点图
图 (a) ~ 图 (c) 是基环树的3种形态,把环缩成一个“虚点”后得到图 (d) ~ 图 (f) 。
请注意,缩成虚点后的无向图 (d) 是一棵树,但是缩成虚点后的有向图 (e) 和图 (f) 不一定是真正的树,如图 (e) 就不是一棵树。
由基环树的特征可知,与基环树有关的题目,首先是找到唯一的环,然后把这个环当作“虚点”。
由前面的无向图的连通性和有向图的连通性可知,基环树的找环问题是“图的连通性”的一个简化问题。
对于无向图, 用拓扑排序的 BFS 可以找出环,操作结束后, 度大于 11 的点就是环上的点。
具体做法:① 计算所有点的度;② 把所有度为11的点入队;③ 从队列弹出度为11的点,把它所连的边去掉,并将边所连的邻居点的度减11,若这个邻居的度变为11,人队;④ 继续执行步骤 ③ 直到队列为空。操作结束后,统计所有点,度数大于 11的点即为环上的点。
注意,这种无向图找环的方法只适用于只有一个环的基环树。
如果只要找环上的一个点, 用 DFS 可以方便地找到。如果有一个点 vv 第 22 次被访问到,那么就存在环,且 vv 是环上的一个点。这个方法可用于有向图和无向图。
例题:
洛谷 P2607 [ZJOI2008] 骑士 :传送锚点
题目描述
Z 国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。
最近发生了一件可怕的事情,邪恶的 Y 国发动了一场针对 Z 国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的 Z 国又怎能抵挡的住 Y 国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。
骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。
战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。
为了描述战斗力,我们将骑士按照 1 至 n 编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
输入格式
第一行包含一个整数 n,描述骑士团的人数。
接下来 n 行,每行两个整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。
输出格式
应输出一行,包含一个整数,表示你所选出的骑士军团的战斗力。
样例
数据规模与约定
对于 30% 的测试数据,满足 n≤10;
对于 60% 的测试数据,满足 n≤100;
对于 80% 的测试数据,满足 n≤104;
对于 100% 的测试数据,满足 1≤n≤106,每名骑士的战斗力都是不大于 106 的正整数。
【算法分析】
本题和“洛谷 P1352-没有上司的舞会”很相似。
洛谷 P1352 例题是一棵树,而本题生成的图是基环树森林。
把 x 讨厌的人 y 设为 x 的父节点,形成从 y 指向 x 的有向边。
本题每个点的入度为 1 ,生成的图中包括很多独立的连通块,每个连通块肯定是一棵基环树,而且形状是一棵外向树。
这些基环树形成了基环树森林。在每棵基环树上找到环,断开这个环后,这棵基环树变成了一棵真正的树,就可以套用洛谷 P1352 的做法了。
本题属于“基环树+树上DP”,下面给出代码。
其中的 DP 代码和洛谷 P1352 一样,这里不再解释。
基环树部分有以下两处。
(1) 找基环树环上一个点。用 check() 函数实现,它是一个 DFS,如果发现某个点被第 2 次访问,它就是环上的一个点,用 bj 记录这个点。
(2) 分别断开 bj 和 bj 的父节点,形成两棵树,在这两棵树上分别做 DP ,取最大值。
Code:
#include<bits/stdc++.h> using namespace std; using ll=long long; const int N=1e6+5; int n,bj,tot; int fa[N],val[N]; int fi[N],ne[N],to[N]; ll ans; ll dp[N][2]; bool vis[N]; void add(int x,int y) { ne[++tot]=fi[x]; fi[x]=tot; to[tot]=y; fa[y]=x; } void dfs(int x) { dp[x][0]=0,dp[x][1]=val[x],vis[x]=1; for(int i=fi[x];i;i=ne[i]) { int v=to[i]; if(v==bj) continue; dfs(v); dp[x][1]+=dp[v][0]; dp[x][0]+=max(dp[v][0],dp[v][1]); } } int check(ll x) { vis[x]=1; return vis[fa[x]]?fa[x]:check(fa[x]); } ll solve(int x) { ll s=0; bj=check(x); dfs(bj); s=max(s,dp[bj][0]); bj=fa[bj]; dfs(bj); s=max(s,dp[bj][0]); return s; } int main() { cin>>n; for(int i=1;i<=n;i++) { int x; cin>>val[i]>>x; add(x,i); } for(int i=1;i<=n;i++) if(!vis[i]) ans+=solve(i); cout<<ans<<'\n'; }
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现