题目大意:一个$n$个点的树,树上有$m$个点对$(a,b)$,找到一个点$x$,使得$max(dis(x,a_i)+dis(x,b_i))$最小
如果做过幻想乡的战略游戏这道题,应该这道题的思路一眼能看出来
首先如果从一个点向能使答案变小的子树上走,那么从子树上一定不会再回到这个点
所以考虑一个暴力,即每次计算所有子树的答案,然后向最优的方向走
这显然是正确的,但是不够优秀
我们再深入分析一下这道题,可以发现,当且仅当所有的距离等于最大值的点对都在它的一个子树内时才可能使得答案变优
很好理解,因为如果不在通一个子树内,不论向任何地方走,总会有点对的最大值变得更大
然后这样我们就可以用点分治的$getroot$来优化这个过程,复杂度为$nlogn$
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define M 100010
using namespace std;
int n,m,num,rt,S,ans=1e9;
int head[M],size[M],maxn[M],bel[M],dis[M],u[M],v[M],st[M];
bool vis[M];
struct point{int to,next,dis;}e[M<<];
void add(int from,int to,int dis)
{
e[++num].next=head[from];
e[num].to=to;
e[num].dis=dis;
head[from]=num;
}
void getroot(int x,int fa)
{
size[x]=maxn[x]=;
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
if(to==fa||vis[to]) continue;
getroot(to,x),size[x]+=size[to];
maxn[x]=max(maxn[x],size[to]);
}
maxn[x]=max(maxn[x],S-size[x]);
if(maxn[x]<maxn[rt]) rt=x;
} void dfs(int x,int fa,int id)
{
bel[x]=id;
for(int i=head[x];i;i=e[i].next)
if(e[i].to!=fa)
{
dis[e[i].to]=dis[x]+e[i].dis;
dfs(e[i].to,x,id);
}
} void solve(int x)
{
if(vis[x]) {printf("%d\n",ans);exit();}
vis[x]=true,dis[x]=;
for(int i=head[x];i;i=e[i].next)
{
dis[e[i].to]=e[i].dis;
dfs(e[i].to,x,e[i].to);
}
int MX=,top=,pos=;
for(int i=;i<=m;i++)
{
if(dis[u[i]]+dis[v[i]]>MX)
{
MX=dis[u[i]]+dis[v[i]];
st[top=]=i;
}
else if(dis[u[i]]+dis[v[i]]==MX)
st[++top]=i;
}
ans=min(ans,MX);
for(int i=;i<=top;i++)
{
if(bel[u[st[i]]]!=bel[v[st[i]]])
{
printf("%d\n",ans);
exit();
}
else
{
if(!pos) pos=bel[u[st[i]]];
else if(pos!=bel[u[st[i]]])
{
printf("%d\n",ans);
exit();
}
}
}
S=size[pos],rt=;
getroot(pos,),solve(rt);
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<n;i++)
{
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
}
for(int i=;i<=m;i++) scanf("%d%d",&u[i],&v[i]);
S=maxn[]=n,getroot(,),solve(rt);
return ;
}