倍增LCA 板子题 还是 hdu 2856 how far away 【个人感觉无比详细】 > <

之前撸了一发离线的lca 就是tarjan算法

这次来一发倍增lca

对于两个节点的lca   这两个节点要到同层 再跳跃倍增找到祖先

其本质是跳跃 2^i ==2^(i-1)+2^(i-1)

文字表述是我跳到八代祖先 我就先跳到四代祖先 再跳四代祖先

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

const int maxn=40020;
int dis[maxn];//根到子节点的距离    
int depth[maxn];//每个节点的深度
int p[maxn][20];
int n,m;

struct node
{
	int to,value;
};

vector<node> vec[maxn];

void dfs(int u,int step,int father)
{
	//标个粑粑 顺便把节点搜索出来 
	depth[u]=step;
	p[u][0]=father;
	for(int i=0;i<vec[u].size();i++)
	{
		int v=vec[u][i].to;
		int w=vec[u][i].value;
		if(v!=father)
		{
			dis[v]=dis[u]+w;
			dfs(v,step+1,u);
		}
	}
}

void init_f()
{
	for(int i=1;(1<<i)<=n;++i)//初始化到最坏情况 就是所有子节点都在一侧
	{
		for(int j=1;j<=n;++j)
		{
			p[j][i]=p[p[j][i-1]][i-1];
		}
	}
}

/*int lca(int u,int v)
{
	if(u==v) return u;
	if(depth[u]<depth[v]) swap(u,v);
	int d=depth[u]-depth[v];
	for(int i=0;(1<<i)<=d;i++)
	{
		if((1<<i)&d) u=p[u][i];
	}
	for(int i=(int)log2(n);i>=0;i--)
	{
		if(p[u][i]!=p[v][i])
		{
			u=p[u][i];
			v=p[v][i];
		}
	}
	return p[u][0];
}*/
int lca(int u,int v)
{
    if(depth[u]<depth[v]) swap(u,v);
    int d=depth[u]-depth[v];
    for(int j=19;j>=0;--j){    
        if((1<<j)&d) //当d为3(11) 1<<j 为 10 就可以跳此时j为1. 当j为0时 1<<j 为1 就总共跳了3次
		//Q:为毛不会重复跳跃同样的次数 如:当d为(1xx)时,跳6下(110) 和4下(100)都会进入if啊?
		//A:他这是跳完之后就直接把这个二进制位直接移走了呀 所以不会重复跳 
            u=p[u][j];      
    }
    /*
	之前这一段 我想的跳到同层的操作是
	for(int i=0;(1<<i)<=d;i++)
	if((1<<j)&d)
	u=p[u][j];
	但其实是错误的
	从1开始起跳
	假如我要跳到上八代祖先是永远也不可能实现的
	因为它是这样跳的嘛 1 2 4 8
	奇数只有一个 根本凑不到8 
	*/  
    if(u==v){   
        return u;
    }
    for(int i=(int)log2(n);i>=0;--i){//这里的logn是最坏情况 即所有元素都在树的一侧 
        if(p[u][i]!=p[v][i]){   //所以直接从高位起跳 加速 
        //Q:跳过头了咋办
		//A:跳过头的元素值是相同的  进不了if 所有u v不会更新 
		//Q:敲里吗 为毛元素是相同的 博主你快讲清楚
		//A:init_f里 假如设根节点为第0层 第一层的节点假如跳两下就过头了 
		//那么p[x][1]=p[p[x][0]][0] 其值还是根节点的父亲 无论跳多少次都是根节点的父亲 
            u=p[u][i];
            v=p[v][i];
        //所以u v的值始终不同 但会随着程序的运行接近lca    
        }
    }
    return p[u][0];    
}
 

int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
		{
			vec[i].clear();
		}
		for(int i=0;i<n-1;i++)
		{
			node tem;
			int u,v,w;
			scanf("%d%d%d",&u,&v,&w);
			tem.to=v;
			tem.value=w;
			vec[u].push_back(tem);
			tem.to=u;
			vec[v].push_back(tem); 
		}
		dis[1]=0;
		dfs(1,0,0);
		init_f();
		for(int i=1;i<=m;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			int ans=dis[u]+dis[v]-2*dis[lca(u,v)];
			printf("%d\n",ans);
		}
	}
} 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值