树的中心
树的中心
定义
- 当一棵树以点 \(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\) 所在的树为第二棵树。
那么我们可以知道,删掉一条边后,一棵树会变成两棵,所以,要连边的话,就要连两棵树的中心,这样才能使直径最小。
那么,连完边后的答案就是:
非常简短。
但是,如何求出点 \(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;
}

浙公网安备 33010602011771号