[NOI2003] 逃学的小孩
题面描述
\(\rm Chris\) 家的电话铃响起了,里面传出了 \(\rm Chris\) 的老师焦急的声音:“喂,是 \(\rm Chris\) 的家长吗?你们的孩子又没来上课,不想参加考试了吗?”一听说要考试,\(\rm Chris\) 的父母就心急如焚,他们决定在尽量短的时间内找到 \(\rm Chris\)。他们告诉 \(\rm Chris\) 的老师:“根据以往的经验,\(\rm Chris\) 现在必然躲在朋友 \(\rm Shermie\) 或 \(\rm Yashiro\) 家里偷玩《拳皇》游戏。现在,我们就从家出发去找 \(\rm Chris\),一但找到,我们立刻给您打电话。”说完砰的一声把电话挂了。
\(\rm Chris\) 居住的城市由 \(N\) 个居住点和若干条连接居住点的双向街道组成,经过街道 \(x\) 需花费 \(T_x\) 分钟。可以保证,任两个居住点间有且仅有一条通路。\(\rm Chris\) 家在点 \(C\),\(\rm Shermie\) 和 \(\rm Yashiro\) 分别住在点 \(A\) 和点 \(B\)。\(\rm Chris\) 的老师和 \(\rm Chris\) 的父母都有城市地图,但 \(\rm Chris\) 的父母知道点 \(A\)、\(B\)、\(C\) 的具体位置而 \(\rm Chris\) 的老师不知。
为了尽快找到 \(\rm Chris\),\(\rm Chris\) 的父母会遵守以下两条规则:
- 如果 \(A\) 距离 \(C\) 比 \(B\) 距离 \(C\) 近,那么 \(\rm Chris\) 的父母先去 \(\rm Shermie\) 家寻找 \(\rm Chris\),如果找不到,\(\rm Chris\) 的父母再去 \(\rm Yashiro\) 家;反之亦然。
- \(\rm Chris\) 的父母总沿着两点间唯一的通路行走。
显然,\(\rm Chris\) 的老师知道 \(\rm Chris\) 的父母在寻找 \(\rm Chris\) 的过程中会遵守以上两条规则,但由于他并不知道 \(A\)、\(B\)、\(C\) 的具体位置,所以现在他希望你告诉他,最坏情况下 \(\rm Chris\) 的父母要耗费多长时间才能找到 \(\rm Chris\)?
分析
定义 \(dis[i][j]\) 表示从点 \(i\) 到点 \(j\) 的花费时间,那么我们令 \(\rm Shermie\) 的家在点 \(A\),\(\rm Yashiro\) 的家在点 \(B\),\(\rm Chris\) 的家在点 \(C\),那么要想得到最短距离,当然要先走错,那么最短距离为:
\(\max\{dis[A][B]\}\) 显然为树的直径,于是 \(O(N)\) 复杂度用 bfs 求出直径即可,再 \(O(N)\) 枚举点 \(C\)。
易证:
\(dist(i,j)\) 表示树上两点 \(i,j\) 的距离,\(away(i)\) 表示点 \(i\) 距离根节点 \(\texttt{root}\) 的距离。
那么再写个倍增求 \(\operatorname{LCA}\) 就好了。
代码
//2021/9/2
//2021/9/3
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#define int long long
#define debug(c) cerr<<#c<<" = "<<c<<endl
namespace Newstd
{
inline int read()
{
char c;
bool flag=false;
while((c=getchar())<'0' || c>'9')
{
if(c=='-') flag=true;
}
int res=c-'0';
while((c=getchar())>='0' && c<='9')
{
res=(res<<3)+(res<<1)+c-'0';
}
return flag?-res:res;
}
inline void print(int x)
{
if(x<0)
{
putchar('-');x=-x;
}
if(x>9)
{
print(x/10);
}
putchar(x%10+'0');
}
}
using namespace Newstd;
using namespace std;
const int ma=200005;
struct Node
{
int v;
int val;
int nxt;
};
Node node[ma<<1];
int head[ma],dis[ma],dep[ma],lg[ma],away[ma];
bool vis[ma];
int fa[ma][20];
int n,m;
int idx,dst;
int st,ed;
inline void add(int u,int v,int w)
{
node[++idx].v=v;
node[idx].val=w;
node[idx].nxt=head[u];
head[u]=idx;
}
inline int bfs(int now)
{
memset(dis,0,sizeof(dis));
memset(vis,false,sizeof(vis));
queue<int>q;
q.push(now);
vis[now]=true;
int maxx=0,pos=0;
while(!q.empty())
{
int u=q.front();
q.pop();
for(register int i=head[u];i;i=node[i].nxt)
{
int v=node[i].v,w=node[i].val;
if(vis[v]==false)
{
vis[v]=true;
dis[v]=dis[u]+w;
if(maxx<dis[v])
{
maxx=dis[v];
pos=v;
}
q.push(v);
}
}
}
return pos;
}
inline void init()
{
for(register int i=1;i<=n;i++)
{
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
}
inline void dfs(int now,int fath)
{
dep[now]=dep[fath]+1;
fa[now][0]=fath;
for(register int i=1;i<=lg[dep[now]];i++)
{
fa[now][i]=fa[fa[now][i-1]][i-1];
}
for(register int i=head[now];i;i=node[i].nxt)
{
int v=node[i].v,w=node[i].val;
if(v!=fath)
{
away[v]=away[now]+w;
dfs(v,now);
}
}
}
inline int LCA(int x,int y)
{
if(dep[x]<dep[y])
{
swap(x,y);
}
while(dep[x]>dep[y])
{
x=fa[x][lg[dep[x]-dep[y]]-1];
}
if(x==y)
{
return x;
}
for(register int k=lg[dep[x]]-1;k>=0;k--)
{
if(fa[x][k]!=fa[y][k])
{
x=fa[x][k];
y=fa[y][k];
}
}
return fa[x][0];
}
inline int dist(int a,int b)
{
return away[a]+away[b]-(away[LCA(a,b)]<<1);
}
#undef int
int main(void)
{
#define int long long
scanf("%lld%lld",&n,&m);
for(register int i=1;i<=m;i++)
{
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
init();
dfs(1,0);
st=bfs(1);
ed=bfs(st);
//现在已经求出树的直径为 dis[dst]
//ans = max{dis[A][C] + dis[A][B]}
//max{dis[A][B]} 已求出
//故需求出 max{dis[A][C]}
//树上枚举即可
int ans=-1;
int d=dist(st,ed);
for(register int i=1;i<=n;i++)
{
ans=max(ans,min(dist(i,st),dist(i,ed))+d);
}
printf("%lld\n",ans);
return 0;
}