基环树学习笔记

基环树

连通、恰好包含唯一一条简单环的无向图。构造操作:一棵树加一条边,就只会多出唯一一个环,得到基环树。满足 m=n

听定义可能会觉得有点晦涩,但其实看图就明白了。图中中间圆圈表示该树唯一的环,三角形表示挂着的正常的子树。

image

性质:任意两点之间恰好有两条简单路径。此外除环上的边外,其余所有边都是割边(桥)其实这东西性质不多,写完了。

找环:

void dfsfid(int u,int fa){
	vis[u]=1; incle[u]=1 ;
	for(auto [v,w]:g[u]) {
		if(v==fa) continue ;
		if(!vis[v]) {dfsfid(v,u) ;continue;}
		else if(incle[v]) {st=v; ed=u ;}
	}
	incle[u]=0 ;
}

直径:

基环树这个东西不是一个标准的树,所以明确本文提到的直径定义为:基环树的环上割掉一条边后,树的直径的最小值。生动形象的:P1399,其实就是这道题的题解。

对于一个基环树,它的直径有以下几种可能:

  1. 在其任意一棵子树中:
    image
  2. 直接经过两个子树:
    image
  3. 绕一圈,然后经过两个子树:
    image

为什么有了3还会有2呢? 其实很显然,就是当 \(l_1>l_2+l_3\) 的时候会出现2这种情况。继续观察发现,只有1与其他两项本质相同。那我们记录一个ans1,去正常跑一遍每个子树的直径。一个ans2,存2和3的两种情况。

树的直径,相比大家都会求,不多解释。主要讲解ans2的情况。貌似根据定义,有一个很显然 \(O(n^2)\),枚举断点答案取大。很显然 \(n^2\) 无法通过。考虑优化。(貌似也很显然呢?)观察2 3两种情况,注意到只有断边在环时才是有解的。那我们只需要枚举环上的断边。
接下来是:

  • \(A[i]\) :在前缀 1~i 中,以某个环上节点为起点,往外走能达到的最远距离(即\(\max(dis_j + 路径长度)\))。

  • \(C[i]\) :在后缀 i~tot 中,以某个环上节点为起点,往外走能达到的最远距离。

  • \(B[i]\) :在前缀 1~i 中,经过两个环上节点的最长折线距离(即 \(dis_x+l_2+l_{...}+l_{i}+ dis_y\) )。

  • \(D[i]\) :在后缀 i~tot 中,经过两个环上节点的最长折线距离。

假设我们在 \(u\)\(v\) 之间断开了边(即 cw[i] 被移除),剩下的所有路径中,最长的那一条只可能是以下三种之一:

  • 只发生在左侧(前缀区间内):最长路径完全在 1~u 内部,即 \(B_u\) 。也就是第2种情况(直接从左侧穿过去)。

  • 只发生在右侧(后缀区间内):最长路径完全在 v~tot 内部,即 \(D_v\) 。也就是第2种情况(直接从右侧穿过去)。

  • 跨越断点(绕一圈):最长路径从左侧穿到右侧,必须经过剩下的那条长边 \(tot1\)。即为 \(A_u\) (左侧最长链) + \(tot1\) (剩下的边) + \(C_v\) (右侧最长链)。也就是第3种情况。

小trick因为直接在环上跑貌似不太好跑,那就拆环成链:那么i就是u,i+1就是v。最终答案也就是三种情况取大 \(\max({B[i], D[i+1], A[i] + C[i+1] + tot1})\)

最后就剩A B C D这四个东西咋求的了。

维护一个变量sum,用于累计从环起点到节点i的距离。那么A就是前i-1个节点的最大值 与 当前节点(距离起点sum)延伸深度 取较大值。

\[A_i=\max{A_{i-1},sum+dist_{cv_{i}}} \]

再维护mx表示前i-1个节点中 dis[j] - sum(起点,j) 的最大值。因为B是最长折线距离,所以易得出:

\[B_i=max{B_{i-1},mx+dist_{cv_i}}+sum \]

前缀都处理好了,用相同的方法倒序处理后缀。这道题就结束了。下面是代码:

#include <bits/stdc++.h>
//#define int long long
using namespace std;

const int N=1e5+10 ;
vector<pair<int,double> > g[N] ;
bool vis[N] ; int fa[N],incle[N],cv[N],tot; double wth[N],cw[N];
bool fid(int u) {
	vis[u]=1 ;
	for(auto [v,w]:g[u]) {
		if(v==fa[u]) continue ;
		fa[v]=u ; wth[v]=w ;
		if(vis[v]==0) {
			if(fid(v)) return 1 ;
			continue ;
		}
		int nu=u ;
		while(1) { //注意这里,要把环上的点存起来
			incle[nu]=1 ; cv[++tot]=nu ;
			cw[tot]=wth[nu] ; 
			nu=fa[nu] ;
			if(nu==u) break ; 
		}
		return 1 ;
	}
	return 0 ;
}

double ans1 ;
double getans1(int u,int f) {
	double mx1=0,mx2=0 ;
	for(auto [v,w]:g[u]) {
		if(v==f||incle[v]) continue ;
		double maxd=getans1(v,u)+w ;
		if(maxd>mx1) {mx2=mx1 ;mx1=maxd ;}
		else if(maxd>mx2) mx2=maxd ; 
	}
	ans1=max(ans1,mx1+mx2) ;
	return mx1 ;
}

double dis[N],A[N],B[N],C[N],D[N];
signed main() {
	ios::sync_with_stdio(0);cin.tie(0); cout.tie(0) ;
	
	int n ; cin>>n ;
	for(int i=1,u,v,w;i<=n;i++) {
		cin>>u>>v>>w ;
		g[u].push_back({v,w}) ; g[v].push_back({u,w}) ;
	}
	
	fid(1) ;
	for(int i=1;i<=tot;i++) dis[cv[i]]=getans1(cv[i],0) ;
	
	double sum=0,mx=0 ;
	for(int i=1;i<=tot;i++) {
		sum+=cw[i-1] ;
		A[i]=max(A[i-1],sum+dis[cv[i]]) ;
		B[i]=max(B[i-1],mx+dis[cv[i]]+sum) ;
		mx=max(mx,dis[cv[i]]-sum) ;
	}
	sum=0; mx=0 ;
	double tot1=cw[tot] ; cw[tot]=0 ;
	for(int i=tot;i>=1;i--) {
		sum+=cw[i] ;
		C[i]=max(C[i+1],sum+dis[cv[i]]) ;
		D[i]=max(D[i+1],mx+dis[cv[i]]+sum) ;
		mx=max(mx,dis[cv[i]]-sum) ;
	}
	
	
	double res,ans2=B[tot] ;
	for(int i=1;i<tot;i++) {
		res=max(max(B[i],D[i+1]),A[i]+C[i+1]+tot1) ;
		ans2=min(ans2,res) ;
	}
	cout<<fixed<<setprecision(1)<<max(ans1,ans2)/2 ;
	
	return 0;
}
posted @ 2026-07-22 20:50  Emumumu  阅读(5)  评论(0)    收藏  举报