# 最近公共祖先：LCA及其用倍增实现 +POJ1986

Q:为什么我在有些地方看到的是最小公共祖先？

A:最小公共祖先是LCA(Least Common Ancestor)的英文直译，最小公共祖先与最近公共祖先只是叫法不同。

Q:什么是最近公共祖先（LCA）？

A:最近公共祖先的概念是很好理解的。首先，你需要脑补出一棵树（它可以是二叉树，也可以是多叉树。）之后，请你再在你脑补出的树上任取两个点。每个点都可以到达树根，且到达的路径是唯一的，既然两个点都可以到达树根，那么根无疑是这两个点的公共祖先。然而，根却不一定是这两个点的最近公共祖先，相反，离根距离最远且在两条点到根路径上的点才是最近公共祖先（最近公共父节点）。

int anc[1005][25];
int fa[1005];
vector <int > tree[1005];
int deep[1005];

void dfs(int x)
{
anc[x][0]=fa[x];
for (int i=1;i<=22;i++)
{
anc[x][i]=anc[anc[x][i-1]][i-1];//倍增思想的体现。不妨在纸上试着画一棵树，脑补一下QWQ
}

for (int i=0;i<tree[x].size();i++)
{
if (tree[x][i]!=fa[x])
{
int y=tree[x][i];
fa[y]=x;//记录父亲节点
deep[y]=deep[x]+1;//记录深度
dfs(y);
}
}
}

int lca(int x,int y)
{
if (deep[x]<deep[y]) _swap(x,y);//我们希望X是较深的点。

for (int i=22;i>=0;i--)//这个循环在完成第一步。
{
if (deep[y]<=deep[anc[x][i]]) //不可以丢掉“=“哦Q^Q
{
x=anc[x][i];
}
}

if (x==y) return x;//如果Y是X的祖先，就可以直接返回结果了。

for (int i=22;i>=0;i--)
{
if (anc[x][i]!=anc[y][i]) //第二步。
{
x=anc[x][i];
y=anc[y][i];
}
}

return anc[x][0];//注意第二步IF语句的条件。
}

Q为什么可以保证倍增就可以刚好到达那个我想要的点呢？

A：你不妨假设你现在点的位置到你想要的点的位置之间距离为D，那么D是整数，那么D一定可以用二进制表示，还记得ANC的第二维代表什么意思吗？二进制数D为1的地方就是我们需要往上翻的地方（比较抽象，适合画一画QAQ），为0的地方我们不动就好。

Distance Queries
Farmer John's cows refused to run in his marathon since he chose a path much too long for their leisurely lifestyle. He therefore wants to find a path of a more reasonable length. The input to this problem consists of the same input as in "Navigation Nightmare",followed by a line containing a single integer K, followed by K "distance queries". Each distance query is a line of input containing two integers, giving the numbers of two farms between which FJ is interested in computing distance (measured in the length of the roads along the path between the two farms). Please answer FJ's distance queries as quickly as possible!

Input

* Lines 1..1+M: Same format as "Navigation Nightmare"

* Line 2+M: A single integer, K. 1 <= K <= 10,000

* Lines 3+M..2+M+K: Each line corresponds to a distance query and contains the indices of two farms.

Output

* Lines 1..K: For each distance query, output on a single line an integer giving the appropriate distance.

Sample Input

7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6
1 4
2 6


Sample Output

13
3
36


Hint

Farms 2 and 6 are 20+3+13=36 apart

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <set>
#include <vector>
#include <cstring>
using namespace std;

int n,m,q;
int up[233333],de[233333],dp[233333][23],fa[233333];
int pn[233333],pg[233333],pv[233333],st[233333];
int tot=0;

void init()
{
memset(up,0,sizeof(up));
memset(de,0,sizeof(de));
memset(dp,0,sizeof(dp));
memset(pn,0,sizeof(pn));
memset(pg,0,sizeof(pg));
memset(pv,0,sizeof(pv));
memset(fa,0,sizeof(fa));

return ;
}

void ins(int x,int y,int w)
{
pv[++tot]=y;
pg[tot]=w;
pn[tot]=st[x];
st[x]=tot;

return;
}

void dfs(int x)
{
dp[x][0]=fa[x];
for(int i=1;i<20;i++)
{
dp[x][i]=dp[dp[x][i-1]][i-1];
}

for (int i=st[x];i;i=pn[i])
{
int cur=pv[i];
if (cur==fa[x]) continue;
de[cur]=de[x]+1;
up[cur]=up[x]+pg[i];
fa[cur]=x;
dfs(cur);
}

return;
}

int lca(int x,int y)
{
if (de[x]<de[y]) {
int t=x;
x=y;
y=t;
}

for (int i=19;i>=0;i--)
{
if (de[dp[x][i]]>=de[y]) x=dp[x][i];
}

if (x==y) return x;

for (int i=19;i>=0;i--)
{
if (dp[x][i]!=dp[y][i])
{
x=dp[x][i];
y=dp[y][i];
}
}

return dp[x][0];
}

int main()
{

while(~scanf("%d%d",&n,&m))
{
init();
int a,b,c;
for (int i=1;i<=m;i++)
{
char s[10];
scanf("%d%d%d%s",&a,&b,&c,s);
ins(a,b,c); ins(b,a,c);
}

fa[1]=1;
de[1]=up[1]=0;
dfs(1);

scanf("%d",&q);

while(q--)
{
scanf("%d%d",&a,&b);

printf("%d\n",up[a]+up[b]-2*up[lca(a,b)]);//求树上距离。
}
}

return 0;
} 

posted @ 2016-01-28 13:51 sllr15 阅读(...) 评论(...) 编辑 收藏