題目描述
給定一棵有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;
}