题解:P5895 [IOI 2013] dreaming 梦想
这个题对我来说最大的帮助在于并查集,而非树的直径(神秘同学并查集 find 写错而且父亲数组不初始化)。
思路
题意就是给你一个森林,用一些边将所有树连起来,求在所有方案中最小的直径长度。
如果自己手玩数据,就可以发现最短的直径所构造出来的图是所有树全连接到一棵树上。
像极了某种能卡 SPFA 的图。
就可以发现这是个菊花图。
那么我们就可以求出每一棵树的直径,找到其半径(即一棵树上所有点的最远距离中最短的长度)。对其进行排序从大到小排序,就可以发现这个题最后的答案无非只有三种情况:
-
就是原本所有树的直径中的最长直径。
-
最大的半径长
+ 第二大的半径长+L 。 -
第二大的半径长
+ 第三大的半径长+2\times L 。
再用并查集维护一下,取最大,这个题就被我们搞定了!
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,l,L,a,pre[100005],fa[100005],d[100005],dis[100005],cnt,ans;
vector<pair<int,int> > e[100005];
int fnd(int x)
{
return x==fa[x]?x:fa[x]=fnd(fa[x]); // 并查集查找。
}
void dfs(int u,int f)
{
pre[u]=f; // 记录每一个点的前驱,后面求半径时要用。
if(dis[u]>=L) L=dis[u],a=u; // 更新直径长度。
for(int i=0;i<e[u].size();i++)
{
int v=e[u][i].first;
int w=e[u][i].second;
if(v==f) continue;
dis[v]=dis[u]+w; // 更新长度数组 dis。
dfs(v,u); // 继续往下搜索。
}
ans=max(ans,L); // 记录所有树中最长的直径,对应情况 1。
}
void solve(int x)
{
L=0; // 初始化直径。
dfs(x,0); // 从根节点开始搜索。
dis[a]=0; // 重置 dis 数组。
L=0; // 重置直径。
dfs(a,0);
int tmp=1e9;
for(int i=a;i;i=pre[i]) tmp=min(tmp,max(dis[i],L-dis[i])); // 对于直径上的所有点,比较其与两个端点长度进行比较取最大值,记录所有点中最大值的最小值。
d[++cnt]=tmp; // 记录每棵树的半径。
}
bool cmp(int a,int b)
{
return a>b;
}
signed main()
{
cin>>n>>m>>l;
for(int i=1;i<=n;i++) fa[i]=i; // fa 数组初始化。
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w; // 读入每条边。
u++; // 特别注意:题目中下是从 0 开始的,如果我们用 1 开始,则输入的编号要 +1。
v++;
e[u].push_back({v,w});
e[v].push_back({u,w}); // 存边。
fa[fnd(u)]=fnd(v); // 合并。
}
for(int i=1;i<=n;i++)
{
if(fa[i]==i) solve(i); // 如果是树的根节点,那么进行搜索并求半径 。
}
sort(d+1,d+cnt+1,cmp); // 将半径由大到小排序。
if(cnt>=2) ans=max(ans,d[1]+d[2]+l); // 对应情况 2。
if(cnt>=3) ans=max(ans,d[2]+d[3]+2*l); // 对应情况 3。
cout<<ans; // 取最大的,输出。
return 0;
}