题目:https://ac.nowcoder.com/acm/contest/12986/E
题意:给出一个 n n n个点 n n n条边的无向连通图,有 q q q次询问,每次询问给出三个点 x , y , z x,y,z x,y,z,求一个点 k k k到 x , y , z x,y,z x,y,z三点距离之和最小。
思路:
首先看到
n
n
n个点
n
n
n条边的无向连通图,这显然是一个基环树,每次询问三个点,那么最多只有三种情况
- 三个点在一棵树上,那么答案就是两两之间距离之和除以 2 2 2
- 三个点分别在两颗树上,那么点
k
k
k的位置一定在同一颗树上两点的
L
c
a
Lca
Lca上。
这个简单证明一下,首先假设 k k k在 L c a ( x , y ) Lca(x,y) Lca(x,y)
k
k
k的位置如果在
L
c
a
(
x
,
y
)
Lca(x,y)
Lca(x,y)下面,会多加一段距离
假设
k
k
k在
L
c
a
(
x
,
y
)
Lca(x,y)
Lca(x,y)到
z
z
z之间,也会有重复距离
- 最后一种情况就是三个点在三棵树上,那么我们就可以抽象成三个在环上的点的问题,就可以直接计算了
代码:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define pb push_back
#define MAXN 100005
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>P;
vector<int>G[MAXN];
int in[MAXN];//入度
int n;
vector<int>vp;//按顺序存环上的点
int vis[MAXN];
int pos[MAXN];//环上的点在vp数组里的下标
int pre[MAXN],dep[MAXN];//记录点的根,点的深度
int fa[MAXN][25];//预处理LCA
void topo()//拓扑排序
{
queue<int>q;
for(int i=1;i<=n;i++)if(in[i]==1)q.push(i);
while(!q.empty())
{
int x=q.front();q.pop();
for(int to:G[x])
{
in[to]--;
if(in[to]==1)q.push(to);
}
}
}
void dfs(int x,int f,int rt)//计算每个点深度,根,预处理fa数组
{
pre[x]=rt;
fa[x][0]=f;
for(int i=1;i<=20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
for(int to:G[x])
{
if(to==f||in[to]>1)continue;
dep[to]=dep[x]+1;
dfs(to,x,rt);
}
}
int lca(int u,int v)
{
if(dep[u]<dep[v])swap(u,v);
for(int i=20;i>=0;i--)if(dep[fa[u][i]]>=dep[v])u=fa[u][i];
if(u==v)return u;
for(int i=20;i>=0;i--)if(fa[u][i]!=fa[v][i])
{
u=fa[u][i];
v=fa[v][i];
}
return fa[u][0];
}
void dfs1(int x)//按顺序把环上的点存进vp
{
vp.pb(x);
vis[x]=1;
for(int to:G[x])
{
if(vis[to]||in[to]!=2)continue;
dfs1(to);
}
}
int main()
{
int q;scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
int u,v;scanf("%d%d",&u,&v);
G[u].pb(v);
G[v].pb(u);
in[u]++;
in[v]++;
pre[i]=i;
}
topo();
for(int i=1;i<=n;i++)
if(in[i]==2)dfs(i,0,i);
for(int i=1;i<=n;i++)
{
if(in[i]==2)
{
dfs1(i);
break;
}
}
for(int i=0;i<(int)vp.size();i++)pos[vp[i]]=i+1;
int sum=vp.size();
while(q--)
{
int x,y,z;scanf("%d%d%d",&x,&y,&z);
int ans=0;
if(pre[x]==pre[y]&&pre[x]==pre[z])//三个点在同一颗树上
{
ans+=dep[x]+dep[y]-2*dep[lca(x,y)];
ans+=dep[x]+dep[z]-2*dep[lca(x,z)];
ans+=dep[z]+dep[y]-2*dep[lca(z,y)];
if(ans%2)ans++;
ans/=2;
}
else if(pre[x]==pre[y])//x,y在一棵树上
{
ans+=dep[x]+dep[y]-dep[lca(x,y)]+dep[z];
int tmp=abs(pos[pre[x]]-pos[pre[z]]);
ans+=min(tmp,sum-tmp);
}
else if(pre[x]==pre[z])//x,z在一棵树上
{
ans+=dep[x]+dep[z]-dep[lca(x,z)]+dep[y];
int tmp=abs(pos[pre[x]]-pos[pre[y]]);
ans+=min(tmp,sum-tmp);
}
else if(pre[y]==pre[z])//y,z在一棵树上
{
ans+=dep[y]+dep[z]-dep[lca(y,z)]+dep[x];
int tmp=abs(pos[pre[y]]-pos[pre[x]]);
ans+=min(tmp,sum-tmp);
}
else//分别在三棵树上
{
ans+=dep[x]+dep[y]+dep[z];
int tmp[5];
tmp[1]=pos[pre[x]];tmp[2]=pos[pre[y]],tmp[3]=pos[pre[z]];
sort(tmp+1,tmp+4);
ans+=min(tmp[3]-tmp[1],min(sum-tmp[3]+tmp[2],sum-tmp[2]+tmp[1]));
}
printf("%d\n",ans);
}
return 0;
}