【倍增法lca】點的距離(鏈式前向星)

題目描述

給定一棵有n個結點的樹,Q個詢問,每次詢問點x到點y亮點之間的距離

輸入

第一行一個n,表示有n個節。

接下來有n-1行,每行2個整數x,y表示x,y之間有一條連邊。

然後一個整數Q,表示有Q次詢問,接下來Q行每行2個整數x,y表示詢問x到y的距離。

輸出

輸出Q行,每行表示每個詢問的結果

樣例輸入

6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6

樣例輸出

3
4

題解

倍增法是由幾個步驟配合完成的:

首先我們對輸入的數據進行建樹,然後對樹進行深度遍歷得到每個節點的深度以及記錄它們的父節點。

然後我們用一個特殊的數組st[i][j]表示節點i上跳2^j次後得到的節點,其中st[i][0]表示第i個節點的父節點,有一個公式是需要理解的,st[ i ][ j ]=st[ st[i][j-1] ][ j-1 ],意思就是第i個節點上跳2^j個節點等於i節點向上跳2^(j-1)的這個節點向上跳2^(j-1)次方(2^j=2^(j-1)+2^(j-1)),j的取值範圍爲[1,log2 n]。

接着就是求兩個節點的最近公共祖先,首先我們要把兩個節點調整到同一深度,具體做法是是使深度大的上跳;當到達同一深度後同時上跳,直到上跳得到的節點相同,那麼這個節點就是它們的最近公共祖先,在程序中用get_lca()來實現。

這題首先求出兩個節點的lca,然後x,y兩點之間的最短路徑就是x->xy的最近公共祖先->y,它們的距離就等於x的深度加上y的深度減去兩倍的公共祖先的深度。

 

關於鏈式前向星,它是通過保存邊來保存一顆樹的,首先用num_edge記錄邊的編號(順序無關)。然後有一個first[i]數組,它保存的是與i點相連的已加入的最後一條邊的num_edge編號(爲什麼這樣保存後面說),然後開一個結構體Edge,next表示下一條邊的num_edge編號,to表示這條邊到達的點。

存儲的方法是:每加入一條邊(i,j),就把這條邊加到i、j鏈表的首部,並更新first[i],first[j]數組。

廣度優先遍歷的方法就是,對於與i相連的所有點,找到最後一次加入的邊的num_edge編號,i是邊的起點(不是真正意義的起點),edge[i].to是邊的終點,通過訪問edge[i].next找到下一條與i相連的邊的編號繼續訪問。

而深度優先遍歷則是找到某條邊的終點(不是真正意義的終點),將這個終點作爲起點,繼續訪問它的終點循環下去。

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=4e5+10;
int num_edge=0,father[maxn],dep[maxn],st[maxn][25];
int first[maxn];//x點最後一條邊的編號
struct Edge
{
	int next;//下一條邊的編號
	int to;//這條邊到達的點
	//int dist;
}edge[maxn*2];
void add_edge(int from,int to)
{
	edge[++num_edge].next=first[from];
	edge[num_edge].to=to;
	first[from]=num_edge;//將這一條邊作爲最後一條邊,下一條邊其實是上一次存儲的邊
}
void dfs(int x,int fa)
{
	father[x]=fa;
	for(int i=first[x];i!=-1;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==fa)
			continue;
		dep[to]=dep[x]+1;
		dfs(to,x);
	}
}
int get_lca(int x,int y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=20;i>=0;i--)
		if(dep[ st[x][i] ]>=dep[y])
			x=st[x][i];
	if(x==y)
		return x;
	for(int i=20;i>=0;i--)
	{
		if(st[x][i]!=st[y][i])
		{
			x=st[x][i];
			y=st[y][i];
		}
	}
	return father[x];
}
int main()
{
	int n,q;
	scanf("%d",&n);
	memset(first,-1,sizeof(first));
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		add_edge(x,y);//鏈式前向星
		add_edge(y,x);
	}
	dep[1]=1;//選作爲根節點
	dfs(1,0);
	for(int i=1;i<=n;i++)
		st[i][0]=father[i];//向上跳一格就是自己的父節點
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++)
			st[i][j]=st[ st[i][j-1] ][ j-1 ];

	scanf("%d",&q);
	while(q--)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		int temp=get_lca(x,y);
		printf("%d\n",dep[x]+dep[y]-2*dep[temp]);
	}
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章