LCA,即LeastCommonAncestor,最近公共祖先.
在上图中,2和3的LCA是1,3和4的LCA也是1.
求LCA的算法怎么样的呢?先来看张图.
欧拉序列F:1 2 5 2 6 2 1 3 1 4 1
深度序列B:0 1 2 1 2 1 0 1 0 1 0
POS:1 2 8 10 3 5
欧拉序列即对树进行DFS过程中得到的DFS序列,深度是欧拉序列中对应的各个节点在树中的深度,
POS是各个节点在DFS序列中第一次出现的位置.如3第一次出现是在欧拉序列的第8个,所以POS(3)=8.
根据DFS的性质,对于两结点u、v,从pos(u)遍历到pos(v)的过程中经过LCA(u, v)有且仅有一次,
且深度是深度序列B[pos(u)…pos(v)]中最小的.
至此,LCA问题转化为RMQ问题.
对于任意两个点U,V的LCA询问,等价于询问DFS序列中POS(U)~POS(V)这段区间内深度最小的节点.
RMQ问题可以用ST算法.效率为O(N+NlogN)-O(1).是非常优秀的离线算法.
(实际上这题的RMQ是特殊的±1RMQ,有O(N)的算法.)
code:(代码来自POJ1470,已AC)
type edge=record
v,n:longint;
end;
const maxn=2000;
maxm=15;
var h,pos,ans:array[0..maxn] of longint;
d,st:array[0..maxn*2] of longint;
f,g:array[0..maxn,0..maxm] of longint;
root:array[0..maxn] of boolean;
e:array[0..maxn*2] of edge;
n,m,i,j,k,u,v,cnt,top:longint;
num:set of char=['0'..'9'];
procedure clean;
begin
cnt:=0; top:=0;
fillchar(h,sizeof(h),0);
fillchar(f,sizeof(f),0);
fillchar(g,sizeof(g),0);
fillchar(d,sizeof(d),0);
fillchar(st,sizeof(st),0);
fillchar(ans,sizeof(ans),0);
fillchar(pos,sizeof(pos),0);
fillchar(root,sizeof(root),1);
end;
procedure add(u,v:longint);
begin
inc(cnt);
e[cnt].v:=v;
e[cnt].n:=h[u];
h[u]:=cnt;
end;
procedure Dfs(u,dep:longint);
var v,p:longint;
begin
inc(top);
st[top]:=u;
d[top]:=dep;
p:=h[u];
while p<>0 do
begin
v:=e[p].v;
dfs(v,dep+1);
inc(top);
st[top]:=u;
d[top]:=dep;
p:=e[p].n;
end;
end;
procedure Prepare;
var i,j,r:longint;
begin
for r:=1 to n do
if root[r] then break;
Dfs(r,1);
for i:=1 to top do
if pos[st[i]]=0 then pos[st[i]]:=i;
for i:=1 to top do
begin
F[i,0]:=d[i];
g[i,0]:=st[i];
end;
for j:=1 to trunc(ln(top)/ln(2)) do
for i:=1 to top-1<<j+1 do
if f[i,j-1]<f[i+1<<(j-1),j-1] then
begin
f[i,j]:=f[i,j-1];
g[i,j]:=g[i,j-1];
end
else
begin
f[i,j]:=f[i+1<<(j-1),j-1];
g[i,j]:=g[i+1<<(j-1),j-1];
end;
end;
procedure swap(var a,b:longint);
var t:longint;
begin t:=a; a:=b; b:=t; end;
function LCA(u,v:longint):longint;
var k:longint;
begin
u:=pos[u];
v:=pos[v];
if u>v then swap(u,v);
k:=trunc(ln(v-u+1)/ln(2));
if f[u,k]<f[v-1<<k+1,k] then LCA:=g[u,k]
else LCA:=g[v-1<<k+1,k]
end;
function getnum:longint;
var c:char;
x:longint=0;
begin
read(c);
while not (c in num) do read(c);
while c in num do
begin
x:=x*10+ord(c)-48;
read(c);
end;
exit(x);
end;
begin
while not seekeof do
begin
Clean;
n:=getnum;
for i:=1 to n do
begin
u:=getnum;
k:=getnum;
for j:=1 to k do
begin
v:=getnum;
add(u,v);
root[v]:=false;
end;
end;
Prepare;
m:=getnum;
for i:=1 to m do
begin
u:=getnum;
v:=getnum;
inc(ans[LCA(u,v)]);
end;
for i:=1 to n do
if ans[i]<>0 then writeln(i,':',ans[i]);
end;
end.
参考资料
2007国家集训队论文 郭华阳《RMQ与LCA问题》