[題解]P9433 [NAPC-#1] Stage5 - Conveyors

Sinktank發表於2024-06-10

P9433 [NAPC-#1] Stage5 - Conveyors

題意簡述

給定一個\(N\)個節點的樹形結構,每條邊有邊權,樹上有\(k\)個關鍵點。

接下來有\(q\)次詢問,每次詢問給定\(x,y\)兩點,請計算從\(x\)開始經過這\(k\)個關鍵點(可以重複經過)再到\(y\)的最短路程。

解題思路

我們可以發現,如果\(x\)\(y\)都在\(k\)個關鍵點所形成的最小子圖中,那麼答案就是這個子圖中所有邊長度之和\(w\)的兩倍,再減去\(x\)\(y\)的距離,即\(2w-dist(x,y)\)

為什麼呢?因為除了\(x\sim y\)之間的邊只需要走\(1\)次,其他邊都需要走來回\(2\)次。

那如果\(x,y\)不全在這個子圖中呢?那麼我們就把\(x,y\)移到裡面去,用\(2w-dist(x',y')\)再額外加上移動的距離即可。

為了方便操作,我們把關鍵點之一當作根節點,這樣該子圖就一定包含根節點了。

接下來,我們就可以用倍增把\(x,y\)跳到子圖中,順便計算出跳躍的距離,累加到\(2w-dist(x',y')\)中即可。

怎麼求跳躍的距離呢?我們用\(dis[u][i]\)表示\(u\)節點往上\(2^i\)層的邊權之和,在預處理LCA時計算出來。倍增跳躍時,每跳一次累加一下\(dis\),最終結果就是跳躍距離了。

怎麼求\(dist(x,y)\)呢?根據上面的定義,\(dis[u][19]\)足以表示\(u\)到根節點的距離,那麼有\(dist(x,y)=dis[x][19]+dis[y][19]-2\times dis[lca(x,y)][19]\)
(一開始我的思路比較傻,是在倍增求LCA的過程中,每跳一步累加一次\(dis\)。這樣顯然不如上面優啦)

時間複雜度\(O((n+q)\log N)\)

\(\texttt{<Code/>}\)

點選檢視程式碼
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int n,k,q,root,w;
struct edge{int to,w;};
vector<edge> G[N];
bool is[N];
int dep[N],fa[N][20],dis[N][20];
void dfs(int u,int father){
	dep[u]=dep[father]+1;
	fa[u][0]=father;
	for(int i=1;i<20;i++)
		fa[u][i]=fa[fa[u][i-1]][i-1],
		dis[u][i]=dis[u][i-1]+dis[fa[u][i-1]][i-1];
	for(auto i:G[u])
		if(i.to!=father){
			dis[i.to][0]=i.w;
			dfs(i.to,u);
			if(is[i.to])
				is[u]=1,
				w+=i.w;
		}
}
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;i>=0;i--)
		if(dep[fa[u][i]]>=dep[v])
			u=fa[u][i];
	if(u==v) return u;
	for(int i=19;i>=0;i--)
		if(fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
int dist(int u,int v){
	return dis[u][19]+dis[v][19]-2*dis[lca(u,v)][19];
}
int jump(int &x){
	int ans=0;
	for(int i=19;i>=0;i--)
		if(fa[x][i]&&!is[fa[x][i]])
			ans+=dis[x][i],
			x=fa[x][i];
	ans+=dis[x][0],x=fa[x][0];
	return ans;
}
int main(){
	cin>>n>>q>>k;
	for(int i=1;i<n;i++){
		int u,v,w;
		cin>>u>>v>>w;
		G[u].push_back({v,w});
		G[v].push_back({u,w});
	}
	for(int i=1;i<=k;i++){
		cin>>root;
		is[root]=1;
	}
	dfs(root,0);
	w*=2;
	while(q--){
		int x,y;
		cin>>x>>y;
		int ans=0;
		if(!is[x]) ans+=jump(x);
		if(!is[y]) ans+=jump(y);
		ans+=w-dist(x,y);
		cout<<ans<<"\n";
	}
	return 0;
}