[題解]P3398 倉鼠找 sugar

Sinktank發表於2024-06-09

P3398 倉鼠找 sugar

題意簡述

給定一個\(N\)個節點的樹形結構。接下來有\(q\)次詢問,每次詢問給定\(4\)個節點\(a,b,c,d\),請計算\(a\)\(b\)的簡單路徑和\(c\)\(d\)的簡單路徑是否有相交的節點。對於每個詢問,輸出Y/N表示答案。

解題思路 & Code

透過手玩樣例可以發現,\(a\sim b\)\(c\sim d\)有相交節點,當且僅當\(lca(a,b)\)\(c\sim d\)上,或者\(lca(c,d)\)\(a\sim b\)上。

那麼我們怎麼判斷一個點在一條路徑上呢?

我們設路徑端點為\(a,b\),要判斷節點\(c\)是否在\(a\sim b\)上。

透過尋找規律,我們可以把“\(c\)\(a\sim b\)上”歸納為下面\(2\)種情況:

  • \(c\)\(a\sim lca(a,b)\)上:\(lca(a,c)=c\)\(lca(a,b)=lca(b,c)\)
  • \(c\)\(b\sim lca(a,b)\)上:\(lca(b,c)=c\)\(lca(a,b)=lca(a,c)\)

簡單證明一下:\(lca(a,c)=c\)說明\(c\)\(a\)的祖先,而\(lca(b,c)=lca(a,b)\)又說明\(lca(a,b)\)\(c\)的祖先。也就是說\(c\)\(a\sim lca(a,b)\)上。\(b\)同理。

只要滿足其一,就說明\(c\)\(a\sim b\)上。我們把判斷的過程寫成函式onpath(a,b,c),對於每次詢問,如果onpath(a,b,lca(c,d))||onpath(c,d,lca(a,b)),則輸出Y,否則輸出N

就醬。

這裡LCA用倍增實現,總時間複雜度\(O((N+q)\log N)\)

點選檢視程式碼
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int dep[N],fa[N][20];
int n,q;
vector<int> G[N];
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];
	for(int i:G[u])
		if(i!=father) dfs(i,u);
}
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 v;
	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];
}
bool onpath(int a,int b,int c){
	int ab=lca(a,b),bc=lca(b,c),ac=lca(a,c);
	return (ac==c&&ab==bc)||(bc==c&&ab==ac);
}
int main(){
	cin>>n>>q;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	while(q--){
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		if(onpath(a,b,lca(c,d))||onpath(c,d,lca(a,b)))
			cout<<"Y\n";
		else cout<<"N\n";
	}
	return 0;
}

附:
給出“\(c\)\(a\sim b\)上”歸納出的兩種情況也可以這樣處理:

  • \(c\)\(a\sim lca(a,b)\)上:\(lca(a,c)=c\)\(dep[c]\ge dep[lca(a,b)]\)
  • \(c\)\(b\sim lca(a,b)\)上:\(lca(b,c)=c\)\(dep[c]\ge dep[lca(a,b)]\)

簡單證明一下:\(lca(a,c)=c\)說明\(c\)\(a\)的祖先,而又有\(dep[c]\ge dep[lca(a,b)]\),將\(c\)限制在了\(a\)\(lca(a,b)\)之間。\(b\)同理。

相關文章