LCA

概念:

两个点最近的共有的祖先节点。

求法:

1.向上标记法

这种算法,emmmmm....可以把它理解为是暴力,即:

假设树上有两个点x,y,那么从x走到根节点,将路过的点都进行标记,再从y向上走到根节点,第一次遇到已经标记过的节点时,就找到了LCA(x,y)。

2.Tarjan求LCA

该算法是一种离线算法,即在一次操作中将问题全部解决。具体过程如下:

1.枚举与当前节点有边相连的所有点,如果该点未被访问过,则从该点开始,返回1。(即dfs遍历每一棵子树)

2.将该节点设为自己的父亲节点,若该点为叶子节点或子节点已全部枚举完毕,则寻找与它有关系的节点,若有与它有关系的点且那个点已经被标记过,则当前它们的父亲节点的编号就是它们的最近公共祖先。

3.将该节点与自己的父亲节点合并。

总复杂度为:O(n+q)

代码如下:

int d[N];
bool vis[N];
void Tarjan(int k) {
	vis[k]=1;
	for(int i=head[k];i;i=next[i]) {
		if(vis[ver[i]]) continue; 
		Tarjan(ver[i]);
		fa[ver[i]]=k;
	}
	for(int i=q_head[k];i;i=q_next[i]) 
		if(vis[q_ver[i]]) 
			ans[q_num[i]]=getfather(q_ver[i]);
} 

例题:

HDU2586 How far away?

题目大意:

有一条n-1条边的树。有m个询问,分别求两点的距离。

分析:

用一个dist数组来表示到根节点的距离,则两点的距离即为dist[i]+dist[j]-2*dist[lca].

代码如下:

#include<cstdio>
#include<string>
//杭电不支持万能头...
using namespace std;
#define N 200001
#define M 200001
int n,m,head[N],q_head[M],ver[N],q_ver[M],nxt[N],q_next[M],tot,q_num[N],q_tot,aska[N],askb[N],edge[N],T;
//杭电next为敏感词...
int fa[N],ans[M],dist[N];
bool vis[N];

inline void add(int x,int y,int z) {
	nxt[++tot]=head[x];
	head[x]=tot;
	ver[tot]=y;
	edge[tot]=z;
}

inline void add_(int x,int y,int number) {
	q_next[++q_tot]=q_head[x];
	q_num[q_tot]=number;
	q_head[x]=q_tot;
	q_ver[q_tot]=y;
}

inline void init() {
	memset(q_head,0,sizeof(q_head));
	memset(vis,0,sizeof(vis));
	memset(head,0,sizeof(head));
	memset(ans,0,sizeof(ans));
	memset(dist,0,sizeof(dist));
	memset(aska,0,sizeof(aska));
	memset(askb,0,sizeof(askb));
	tot=0,q_tot=0;
    //初始化
	scanf("%d%d",&n,&m);
	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);
	}
	for(int i=1;i<=m;i++) {
		int x,y;
		scanf("%d%d",&x,&y);
		add_(x,y,i);
		add_(y,x,i);
		aska[i]=x,askb[i]=y;
	}
	for(int i=1;i<=N;i++) fa[i]=i;
}

inline int getfather(int x) {
	if(x==fa[x]) return x;
	return fa[x]=getfather(fa[x]);
}

void Tarjan(int k) {
	vis[k]=1;
	for(int i=head[k];i;i=nxt[i]) {
		if(vis[ver[i]]) continue; 
		dist[ver[i]]=dist[k]+edge[i];
		Tarjan(ver[i]);
		fa[ver[i]]=k;
	}
	for(int i=q_head[k];i;i=q_next[i]) 
		if(vis[q_ver[i]]) 
			ans[q_num[i]]=getfather(q_ver[i]);
} 

void work() {
	Tarjan(1);
}

inline void outo() {
	for(int i=1;i<=m;i++) printf("%d\n",dist[aska[i]]+dist[askb[i]]-2*dist[ans[i]]);
}
		
int main() {
	scanf("%d",&T);
	for(int i=1;i<=T;i++) {
		init();
		work();
		outo();
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值