树的中心

树的中心

定义

  • 当一棵树以点 \(x\) 为根时,树的高度(深度)最小,则称 \(x\) 为数的中心。

性质

  • 树的中心不唯一,但是至多只有 \(2\) 个点为树的中心。

  • 树的中心若有多个,则它们一定相邻。

  • 树上的所有点到其最远端的路径,一定交汇于树的中心。

  • 树的中心做根节点时,从树的中心出发的最长链与次长链构成树的直径。

  • 树的中心一定在树的直径上。

  • 叶子节点加一条边或删一条边,树的中心至多偏移一个点。

求法

  • 维护每个点 \(x\) 出发的最长链,当最长链最短时,\(x\) 就是树的中心。

  • 维护每个点 \(x\) 内的最长链 \(dis1_x\) 以及子树外的最长链 \(up_x\)

  • 由于 \(up_x\) 可以继承 \(up_{fa}\),所以要避免 \(up_x\)\(x\) 的子树内走。

  • 需要维护每个点 \(x\) 子树内的次长链 \(dis2_x\),当 \(dis1_{fa} = dis1_x + w\) 时,则 \(up_x\) 应与 \(dis2_{fa}\) 拼接。

时间复杂度 \(O(n + m)\)

例题

Luogu - U392706

解法

板子题,需要一个 Search_Down 函数求解最长链与次长链,还有一个 Search_Up 函数求解 \(up_x\)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,dis1[N],dis2[N],up[N]; 
vector<pair<int,int>> g[N]; //存边
void Search_Down(int u,int fa)
{
	for(auto V:g[u])
	{
        int v=V.first,w=V.second;
		if(v!=fa)
		{
			Search_Down(v,u);
			if(dis1[v]+w>dis1[u]) //可以更新最长链
			{
				dis2[u]=dis1[u];
				dis1[u]=dis1[v]+w;
			}
			else if(dis1[v]+w>dis2[u]) //可以更新次长链
			{
				dis2[u]=dis1[v]+w;
			}
		}
	}
	return;
}
void Search_Up(int u,int fa)
{
	for(auto V:g[u])
	{
        int v=V.first,w=V.second;
		if(v!=fa)
		{
			up[v]=up[u]+w; //直接继承
			if(dis1[u]==dis1[v]+w) up[v]=max(up[v],dis2[u]+w); //若无法继承最长链(往当前节点的子树内走的),则继承次长链
			else up[v]=max(up[v],dis1[u]+w); //继承次长链
			Search_Up(v,u);
		}
	}
	return;
}
signed main()
{
	cin.tie(0)->sync_with_stdio(false);
	cin>>n;
	for(int i=1,x,y,z;i<n;++i)
	{
		cin>>x>>y>>z;
		g[x].emplace_back(y,z);
		g[y].emplace_back(x,z);
	}
	Search_Down(1,-1),Search_Up(1,-1); //搜索
	int Min=1e18;
	for(int i=1;i<=n;++i) Min=min(Min,max(up[i],dis1[i]));
	for(int i=1;i<=n;++i)
	{
		if(max(up[i],dis1[i])==Min) cout<<i<<'\n';
	}
	return 0;
}

HDU - 3721

题意简化

题目给了我们一棵树,从 \(0\) 编号至 \(n - 1\),然后可以将一条边删去,并在其它任意两个点之间加一条边,使得操作后仍然是一棵树,求操作后的直径最小。

解法

一眼发现 \(T \le 10 , n \le 2500\),直接 \(O(n ^ 2)\) 暴力。

首先枚举 \(n - 1\) 条边,就是枚举删去的边,假设边的左边是 \(u\),右边是 \(v\),权值是 \(w\)\(u\) 所在的树为第一棵树,\(v\) 所在的树为第二棵树。

那么我们可以知道,删掉一条边后,一棵树会变成两棵,所以,要连边的话,就要连两棵树的中心,这样才能使直径最小。

那么,连完边后的答案就是:

\[max(\small{\text{第一棵树的直径}} , \small{\text{第二棵树的直径}} , \small{\text{两棵树的中心出发的最长链之和}} + w) \]

非常简短。

但是,如何求出点 \(i\) 是属于哪棵树的呢?直接把 \(u\) 能到的所有点标记为 \(1\),则标记为 \(1\) 的是第一棵树的,否则就是第二棵树的。

然后对答案取最小值即可。

注意还要和一开始什么都不干时的直径取最小值。

代码

#include <bits/stdc++.h>
#define fi first
#define se second

using namespace std;
using Pii = pair<int, int>;

const int N = 2505;
int n, Cnt_Test, Dist1[N], Dist2[N], Up[N];
bool vis[N];
pair<Pii, int> Edge[N]; //所有边的信息
vector<Pii> g[N]; //存边

void Mark(int u, int fa) //Mark 进行标记
{
	vis[u] = 1;
	for (Pii V : g[u])
	{
		int v = V.fi, w = V.se;
		if (v != fa)
		{
			Mark(v, u);
		}
	}
	return;
}

void Search_Down(int u, int Fa) //Search_Down 求解最长链及次长链
{
	for (Pii V : g[u])
	{
		int v = V.fi, w = V.se;
		if (v != Fa)
		{
			Search_Down(v, u);
			if (Dist1[v] + w > Dist1[u]) //可以更新最长链
			{
				Dist2[u] = Dist1[u];
				Dist1[u] = Dist1[v] + w;
			}
			else if (Dist1[v] + w > Dist2[u]) //不能更新,则更新次长链
			{
				Dist2[u] = Dist1[v] + w;
			}
		}
	}
	return;
}

void Search_Up(int u, int Fa) //Search_Up 求解子树外的最长链
{
	for (Pii V : g[u])
	{
		int v = V.fi, w = V.se;
		if (v != Fa)
		{
			Up[v] = Up[u] + w; //直接继承
			if (Dist1[u] == Dist1[v] + w) //最长链在当前子树内,无法继承最长链
			{
				Up[v] = max(Up[v], Dist2[u] + w);
			}
			else //可以继承最长链
			{
				Up[v] = max(Up[v], Dist1[u] + w);
			}
			Search_Up(v, u);
		}
	}
	return;
}

void Solve_Test()
{
	for (int i = 1; i <= n; ++i)
	{
		g[i].clear();
		Dist1[i] = Dist2[i] = Up[i] = vis[i] = 0;
	}
	cin >> n;
	for (int i = 1, x, y, z; i < n; ++i)
	{
		cin >> x >> y >> z;
		++x, ++y;
		g[x].emplace_back(y, z), g[y].emplace_back(x, z);
		Edge[i] = {{x, y}, z};
	}
	int Min = 0;
	Search_Down(1, -1);
	for (int i = 1; i <= n; ++i)
	{
		Min = max(Min, Dist1[i] + Dist2[i]); //这里记得先求一遍
	}
	for (int i = 1; i < n; ++i)
	{
		for (int j = 1; j <= n; ++j)
		{
			g[j].clear();
			Dist1[j] = Dist2[j] = Up[j] = vis[i] = 0; //清空
		}
		for (int j = 1; j < n; ++j)
		{
			if (i == j) //如果不是一样的,就加边
			{
				continue;
			}
			g[Edge[j].fi.fi].emplace_back(Edge[j].fi.se, Edge[j].se); //加边
			g[Edge[j].fi.se].emplace_back(Edge[j].fi.fi, Edge[j].se);
		}
		Search_Down(Edge[i].fi.fi, -1); //求解两棵树的最长链及次长链
		Search_Down(Edge[i].fi.se, -1);
		Search_Up(Edge[i].fi.fi, -1); //求解两棵树的子树外最长链
		Search_Up(Edge[i].fi.se, -1);
		Mark(Edge[i].fi.fi, -1); //进行标记是哪一棵树的
		int Min1 = 2e9, Min2 = 2e9, Max1 = 0, Max2 = 0; //Min1是第一课树的从中心出发的最长链,Min2是第二课树的,Max1是第一课树的直径,Max2是第二棵树的
		for (int j = 1; j <= n; ++j)
		{
			if (vis[j]) //第一棵树
			{
				Min1 = min(Min1, max(Dist1[j], Up[j]));
				Max1 = max(Max1, Dist1[j] + Dist2[j]);
			}
			else //第二棵树
			{
				Min2 = min(Min2, max(Dist1[j], Up[j]));
				Max2 = max(Max2, Dist1[j] + Dist2[j]);
			}
		}
		Min = min(Min, max({Max1, Max2, Min1 + Min2 + Edge[i].se})); //答案取最小值
	}
	cout << "Case " << (++Cnt_Test) << ": " << Min;
	return;
}

int main()
{
	cin.tie(0)->sync_with_stdio(false);
	int T;
	cin >> T;
	for (; T--; cout << '\n')
	{
		Solve_Test();
	}
	return 0;
}
posted @ 2025-03-09 17:22  BUMIE  阅读(106)  评论(0)    收藏  举报