题意
勇气小镇是一个有着n个房屋的小镇,为什么把它叫做勇气小镇呢,这个故事就要从勇气小镇成立的那天说起了,
修建小镇的时候,为了让小镇有特色,镇长特地只修了n-1条路,并且规定说,所有在勇气小镇的村民,每一次出门必须规划好路线,
路线必须满足在到达终点之前绝对不走回头路。每个人都要这样,不然那个人就不配在小镇生活下去,因为他没有这个勇气。
事实上,这并不能算一项挑战,因为n-1条路已经连通了每户人家,不回头地从起点到终点,只是一个时间上的问题。
由于小镇上的福利特别好,所以小懒入住了这个小镇,他规划了m次的行程,每次从L房屋到R房屋,他想问你他每次从L房屋到R房屋最少能走多少路。
解题
显然沿着L–>lca(L,R)–>R路径中是最优的。设d[u]表示节点u到根节点的距离。通过一次dfs预处理出所有节点到根节点的距离以及所有L,R的lca。然后根据查询顺序(在query边中的编号)输出d[L]+d[R]-2*d[lca]即可。
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=4e4+100;
const int maxm=220;
int d[maxn];//d[u]表示u结点距根的距离
struct edge
{
int from,to,w,next,lca;
}e[maxn<<1],q[maxm<<1];
int par[maxn],n;
int ance[maxn];
int head[maxn],cnt,first[maxn],tot,vis[maxn];
void add_edge(int u,int v,int w)//树图连边
{
e[++cnt].to=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
void _add(int u,int v)//查询连边,都是单向边
{
q[++tot].to=v;
q[tot].from=u;
q[tot].next=first[u];
first[u]=tot;
}
void init()//边以及并查集的初始化
{
memset(head,-1,sizeof(head));
memset(first,-1,sizeof(first));
memset(vis,0,sizeof(vis));
for(int i=0;i<=n;i++) par[i]=i;
tot=-1;//查询边的编号从0开始
cnt=-1;//树图边的编号从0开始
}
int find(int x)//查询祖先结点
{
return par[x]==x?x:par[x]=find(par[x]);
}
void unit(int x,int y)//合并
{
int fx=find(x),fy=find(y);
if(fx==fy) return ;
par[fx]=fy;
}
void tarjan(int u)//dfs+并查集
{
vis[u]=1;//标记已访问
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(vis[v]) continue;
d[v]=d[u]+e[i].w;//在dfs的同时维护d
tarjan(v);//将v子树dfs完
unit(v,u);//合并v子树到u结点
}
for(int i=first[u];i!=-1;i=q[i].next)
{
int v=q[i].to;
if(!vis[v]) continue;
q[i].lca=q[i^1].lca=find(v);
}
}
int main()
{
int T,m;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
init();
for(int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
_add(u,v);
_add(v,u);
}
d[1]=0;
tarjan(1);
for(int i=0;i<m;i++)
{
int id=i*2,u=q[id].from,v=q[id].to,lca=q[id].lca;
printf("%d\n",d[u]+d[v]-2*d[lca]);
}
}
return 0;
}