12.16+树上倍增法求LCA

65 篇文章 0 订阅
37 篇文章 0 订阅

树上倍增求LCA的步骤:

1.预处理:节点的深度d、到根节点的距离dist、该点向上走2^k步能够到达的点:f数组。

2.lca:

①.将两个节点调整到同一个深度,只调整深的那个即可。

②.如果①结束后,这两个点重合,说明该点就是所求的点。

③.否则,从大往小开始试跳跃的步数,直至将这两个点调整为目标点的两个子节点。

最后两点的父节点就是所求的LCA。

 

 

例题:HDU 2568:

http://acm.hdu.edu.cn/showproblem.php?pid=2586

题意:给一颗n个点的树以及n-1条边的权值,m次查询,查询指定两个点之间的距离。

分析:

      树之间的路径肯定就是两点之间的最短路了,但是如果弗洛伊德算法40000*40000*10会超时,

dijkstra算法的话15*200*40000*10也会超时。所以那几个求最短路的算法都不能用了。

       然后根据题解,用 LCA的方法时间复杂度 o((N+M)logN)。

     因为x到y的距离,可以看做x到根节点的距离+y到根节点的距离-2*LCA到根节点的距离

所以可以先求LCA,又因为树作为特殊的图,两节点之间有且仅有一条通路且一定为最短路,

所以求深度、求最短路的操作是可以同时进行的(spfa,dijkstra均可以),最后,套用上面

的公式即出结果。

 

AC代码:

#define ll long long
using namespace std;
const int SIZE=50010;
int f[SIZE][20],d[SIZE],dist[SIZE];
int ver[2*SIZE],Next[2*SIZE],edge[2*SIZE],head[SIZE];
int T,n,m,tot,t;
queue <int> q;
void add(int x,int y,int z)
{
    ver[++tot]=y;
    edge[tot]=z;
    Next[tot]=head[x];
    head[x]=tot;
}
void bfs()//预处理,求深度、最短路,f数组;就是SPFA,时间复杂度O(NlogN)
{
    q.push(1);
    d[1]=1;
    while(q.size())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if(d[y])continue;
            d[y]=d[x]+1;   //求节点的深度;
            dist[y]=dist[x]+edge[i];//距离根节点的距离;
            f[y][0]=x;
            for(int j=1;j<=t;++j)
            {
                f[y][j]=f[f[y][j-1]][j-1];
        //动态规划,y点 跳(2^j)步 能到达哪个点。
                //cout<<y<<" "<<j<<" "<<f[y][j]<<endl;
                q.push((y));
            }
        }
    }
}
int lca(int x,int y)//求LCA,时间复杂度:o(Log(N))
{
    if(d[x]>d[y])swap(x,y);
    for(int i=t;i>=0;--i)//倒着走******
        if(d[f[y][i]]>=d[x])y=f[y][i];
    if(x==y)return x;
    for(int i=t;i>=0;--i)
        if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
                 //注意一下:x是不断变化的,而i整个一套下来;
    return f[x][0];//一直更新,直至x、y为目标点下面的那两个点;
}
int main()
{
    cin>>T;
    while(T--)
    {
        cin>>n>>m;
        t=(int)(log(n))/log(2)+2; 
            //这里限制向上的最大步数,为什么是这个?不知道.
        cout<<t<<endl;
        for(int i=0;i<=n;++i)head[i]=d[i]=0;
        tot=0;
        for(int i=1;i<n;++i)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            add(y,x,z);
        }
        bfs();
        //就是一个求最短路的spfa,同时求深度和f数组的值
        while(m--)
        {
            int x,y;
            cin>>x>>y;
            cout<<dist[x]+dist[y]-2*dist[lca(x,y)]<<endl;

        }

    }
    return 0;
}

     

 

The end;

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值