P10289 [GESP樣題 八級] 小楊的旅遊 題解

zhuluoan發表於2024-04-14

題目簡述

給定一棵樹,節點之間的距離為 $1$,樹上有 $k$ 個傳送門,可以從一個傳送門瞬間傳送到另一個傳送門,有 $q$ 此詢問,問 $u$ 和 $v$ 之間的最短距離是多少。

題目分析

首先考慮沒有傳送門,我們可以直接求兩個點的 LCA,再用高度容斥計算即可。

如果有傳送門,那麼有用與不用兩種選擇,如果用的話,那麼最優的方法一定是 $u$ 到離它最近的傳送門的距離,再加上 $v$ 到離它最近的傳送門的距離,這個可以直接用 bfs 預處理,最後在與不用的法案比較一下。

程式碼

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,k,Q,deep[N],fa[N][40],dis[N]; 
vector<int> G[N];
queue<int> q;
void dfs(int u,int father)
{
	deep[u]=deep[father]+1;
	fa[u][0]=father;
	for(int i=1;i<=30;i++)
	    fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int v:G[u])
	    if(v!=father) dfs(v,u);
	return;
}
int lca(int u,int v)
{
	if(deep[u]<deep[v])
	    swap(u,v);
	int temp=deep[u]-deep[v];
	for(int i=0;i<=30;i++)
	    if(temp>>i&1)
		    u=fa[u][i];
	if(u==v) return u;
	for(int i=30;i>=0;i--)
	    if(fa[u][i]!=fa[v][i])
		    u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
void bfs()
{
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int v:G[u])
		    if(dis[v]==dis[0])
		        dis[v]=dis[u]+1,q.push(v);
	}
	return;
}
int main()
{
	ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>k>>Q;
    memset(dis,0x3f,sizeof dis);
    for(int x=0,y=0,i=1;i<n;i++)
    {
    	cin>>x>>y;
    	G[x].push_back(y);
    	G[y].push_back(x);
	}
	dfs(1,0);
	for(int x=0,i=1;i<=k;i++)
	    cin>>x,q.push(x),dis[x]=0;
    bfs();
	for(int x=0,y=0,i=1;i<=Q;i++)
	    cin>>x>>y,cout<<min(dis[x]+dis[y],deep[x]+deep[y]-2*deep[lca(x,y)])<<"\n";
	return 0;
}

相關文章