NOI2003 逃學的小孩 題解

Atserckcn發表於2024-08-20

NOI2003 逃學的小孩 題解

傳送門。

題目簡述

給定一棵樹 \(T\),需要選擇三個點 \(A,B,C\),需要從 \(C\) 走到 \(A,B\)​​ 的最遠距離。

(第一段題目是在講劇情嗎。。)

前置知識

  • 樹的直徑

思路簡述

這題在藍題(提高+ / 省選-)中還是比較水的 ^_^

來看看樣例吧

瞪眼法(——數學老師) 看看,發現 \(A,B\) 可以設在 \(1\)\(4\),然後 \(C\)\(2\)\(3\) 都無所謂。

那麼 \(4\) 是咋來的呢?

(設 \(C\)\(2\)

\(2\rightarrow 1 \rightarrow4\)

由於是最遠距離,那麼——

樹的直徑!

而剛好,樹的直徑就是有兩個端點,剛剛好可以一個作為 \(A\),一個作為 \(B\)

然後 \(C\) 就是在除了 \(A,B\) 的節點,距離 \(A,B\) 的最短路徑。

那麼,直接列舉所有 \(C\),取最大值再加上 \(A\rightarrow B\) 的距離(直徑距離)即可。

程式碼實現

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e5+5;
ll n,m,head[N],cnt_e,u,v,w,top,dis_start[N],dis_stop[N],start,stop,ans,ans2;
struct E{
	ll from,to,w,pre;
}e[N<<1];
inline void add(ll from,ll to,ll w)//鏈式前向星
{
	e[++cnt_e].from=from;
	e[cnt_e].to=to;
	e[cnt_e].w=w;
	e[cnt_e].pre=head[from];
	head[from]=cnt_e;
	return;
}
void dfs_d(ll u/*當前節點*/,ll fa/*他爹*/,ll sum/*目前的最長路徑*/)//求樹的直徑
{
	if(sum>ans)
		ans=sum,top=u;
	for(ll i=head[u];i;i=e[i].pre)
	{
		ll v=e[i].to;
		if(v==fa) continue;
		dfs_d(v,u,sum+e[i].w);
	}
	return;
}
void dfs_dis_start(int u,int fa)//所有點到某個端點的距離
{
	for(ll i=head[u];i;i=e[i].pre)
	{
		ll v=e[i].to;
		if(v==fa) continue;
		dis_start[v]=dis_start[u]+e[i].w;
		dfs_dis_start(v,u);
	}
	return;
}
void dfs_dis_stop(int u,int fa)//所有點到另一個端點的距離
{
	for(ll i=head[u];i;i=e[i].pre)
	{
		ll v=e[i].to;
		if(v==fa) continue;
		dis_stop[v]=dis_stop[u]+e[i].w;
		dfs_dis_stop(v,u);
	}
	return;
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	dfs_d(1,0,0);
	start=top;ans=0;
	dfs_d(start,0,0);
	stop=top;
	dfs_dis_start(start,0);
	dfs_dis_stop(stop,0);
	for(ll i=1;i<=n;i++)//列舉所有可能的C
		ans2=max(ans2,min(dis_start[i],dis_stop[i]));
	printf("%lld\n",ans+ans2);
    //ans:直徑距離
    //ans2:某個點到兩個端點的最短距離
	return 0;
}

小彩蛋

我:不對勁,有問題:

\(1\le T_i \le 10^9\)

十億分鐘。。。先不說你能不能活到那時候,就算能考試貌似就已經結束了吧。。

相關文章