图论基础_LCA

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问题》

转载于:https://www.cnblogs.com/exponent/archive/2011/08/09/2132320.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值