震惊!LCA竟然还能用这四种方法求?学到了!不看后悔一辈子!

模板题:洛谷P3379

倍增(在线)

a n c [ u ] [ i ] anc[u][i] anc[u][i]表示节点 u u u向上跳 2 i 2^i 2i到达哪个节点, d e e p [ u ] deep[u] deep[u]表示 u u u的深度。
每次向上让 u , v u,v u,v跳到同一深度,再向上跳到 L C A LCA LCA
优点:容易理解,容易敲
缺点:常数大qwq
时间复杂度:预处理 O ( n l o g n ) O(nlogn) O(nlogn),查询每次 O ( l o g n ) O(logn) O(logn)
空间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

#include<stdio.h>
#include<algorithm>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=500005;
int n,m,s,cnt,head[Size];
struct Edge {
	int v,next;
} w[Size<<1];
void AddEdge(int u,int v) {
	w[++cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
int deep[Size],anc[Size][21];
void dfs(int x,int fa) {
	deep[x]=deep[fa]+1;
	anc[x][0]=fa;
	for(re i=1; i<=20; i++) {
		anc[x][i]=anc[anc[x][i-1]][i-1];
	}
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		if(nxt!=fa) {
			dfs(nxt,x);
		}
	}
}
int LCA(int u,int v) {
	if(deep[u]<deep[v])	swap(u,v);
	for(re i=20; i>=0; i--) {
		if(deep[anc[u][i]]>=deep[v]) {
			u=anc[u][i];
		}
	}
	if(u==v)	return u;
	for(re i=20; i>=0; i--) {
		if(anc[u][i]!=anc[v][i]) {
			u=anc[u][i];
			v=anc[v][i];
		}
	}
	return anc[u][0];
}
int main() {
	n=read();
	m=read();
	s=read();
	for(re i=1; i<n; i++) {
		int u=read();
		int v=read();
		AddEdge(u,v);
		AddEdge(v,u);
	}
	dfs(s,0);
	while(m--) {
		int u=read();
		int v=read();
		printf("%d\n",LCA(u,v));
	}
	return 0;
}

树链剖分(在线)

对于树上每个节点 u u u,一遍 d f s dfs dfs处理出它的子树大小 s i z siz siz,它的重儿子 s o n son son
然后再跑一遍 d f s dfs dfs,记录每个节点的 t o p top top
每次询问时向上不断跳到 f a t h e r [ t o p ] father[top] father[top]的位置即可。
优点:常数小,预处理时间复杂度低,空间复杂度低
缺点:容易出 b u g bug bug
时间复杂度:预处理 O ( n ) O(n) O(n),查询每次 O ( l o g n ) O(logn) O(logn)
空间复杂度: O ( n ) O(n) O(n)
upd:洛谷上没卡树剖,树剖跑得还比倍增快,是之前写的树剖有误qwq

#include<stdio.h>
#include<algorithm>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=500005;
int n,m,s,cnt,head[Size];
struct Edge {
	int v,next;
} w[Size<<1];
void AddEdge(int u,int v) {
	w[++cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
int deep[Size],top[Size],father[Size],siz[Size],son[Size];
void dfs(int x,int fa) {
	deep[x]=deep[fa]+1;
	father[x]=fa;
	siz[x]=1;
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		if(nxt!=fa) {
			dfs(nxt,x);
			siz[x]+=siz[nxt];
			if(siz[nxt]>siz[son[x]]) {
				son[x]=nxt;
			}
		}
	}
}
void dfs2(int x,int tp) {
	top[x]=tp;
	if(!son[x])	return;
	dfs2(son[x],tp);
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		if(nxt!=father[x] && nxt!=son[x]) {
			dfs2(nxt,nxt);
		}
	}
}
int LCA(int u,int v) {
	while(top[u]!=top[v]) {
		if(deep[top[u]]>=deep[top[v]]) {
			u=father[top[u]];
		} else {
			v=father[top[v]];
		}
	}
	if(deep[u]<deep[v])	return u;
	return v;
}
int main() {
	n=read();
	m=read();
	s=read();
	for(re i=1; i<n; i++) {
		int u=read();
		int v=read();
		AddEdge(u,v);
		AddEdge(v,u);
	}
	dfs(s,0);
	dfs2(s,s);
	while(m--) {
		int u=read();
		int v=read();
		printf("%d\n",LCA(u,v));
	}
	return 0;
}

LCA转RMQ(在线)

在这里插入图片描述
考虑在上面的图中,在 d f s dfs dfs中遍历每条边时把起点和终点加入序列,得到序列:
1 , 5 , 1 , 2 , 4 , 2 , 3 , 2 , 1. 1,5,1,2,4,2,3,2,1. 1,5,1,2,4,2,3,2,1.
记录下每个节点最开始出现的位置 d f n dfn dfn,然后对于询问 u , v u,v u,v,转化求成 d f n [ u ] , d f n [ v ] dfn[u],dfn[v] dfn[u],dfn[v]之间的深度最小的节点即可。
静态最值,用 S T ST ST表解决。
优点:查询复杂度低。
缺点:还没想到qwq
时间复杂度:预处理 O ( n l o g n ) O(nlogn) O(nlogn),查询 O ( 1 ) O(1) O(1)
空间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
注:如果想要减小常数,珂以把 l o g 2 log_2 log2的值都预处理出来,就不用每次调用 l o g 2 ( ) log2() log2()函数。

#include<stdio.h>
#include<algorithm>
#include<math.h>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=1000005;
int n,m,s,cnt,head[Size];
struct Edge {
	int v,next;
} w[Size<<1];
void AddEdge(int u,int v) {
	w[++cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
int tot,deep[Size],dfn[Size],order[Size];
void dfs(int x,int fa) {
	deep[x]=deep[fa]+1;
	dfn[x]=++tot;
	order[tot]=x;
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		if(nxt!=fa) {
			dfs(nxt,x);
			order[++tot]=x;
		}
	}
}
int stmin[Size][21];
int LCA(int u,int v) {
	int l=dfn[u],r=dfn[v];
	if(l>r)	swap(l,r);
	int x=log2(r-l+1);
	if(deep[stmin[r][x]]<deep[stmin[l+(1<<x)-1][x]]) {
		return stmin[r][x];
	} else {
		return stmin[l+(1<<x)-1][x];
	}
}
int main() {
	n=read();
	m=read();
	s=read();
	for(re i=1; i<n; i++) {
		int u=read();
		int v=read();
		AddEdge(u,v);
		AddEdge(v,u);
	}
	dfs(s,0);
	for(re i=1; i<=tot; i++) {
		stmin[i][0]=order[i];
	}
	for(re j=1; j<=20; j++) {
		for(re i=1<<j; i<=tot; i++) {
			if(deep[stmin[i][j-1]]<deep[stmin[i-(1<<(j-1))][j-1]]) {
				stmin[i][j]=stmin[i][j-1];
			} else {
				stmin[i][j]=stmin[i-(1<<(j-1))][j-1];
			}
		}
	}
	for(re i=1; i<=tot; i++) {
		printf("%d ",order[i]);
	}
	while(m--) {
		int u=read();
		int v=read();
		printf("%d\n",LCA(u,v));
	}
	return 0;
}

tarjan(离线)

这篇博客怎么还没写完……现在有点想睡觉……
鸽了qwq
想学的话珂以看这篇题解
优点:时空复杂度均优秀
缺点:必须离线
时间复杂度:预处理 O ( n ) O(n) O(n),每次查询 O ( 1 ) O(1) O(1)
空间复杂度: O ( n ) O(n) O(n)

#include<stdio.h>
#include<algorithm>
#include<math.h>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=500005;
int n,m,s,cnt,tot,head[Size],Qhead[Size];
int ans[Size];
struct Edge {
	int id,v,next;
} w[Size<<1],Q[Size<<1];
void AddEdge(int u,int v) {
	w[++cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
void AddQuery(int u,int v) {
	Q[++tot].v=v;
	Q[tot].id=(tot+1)>>1;
	Q[tot].next=Qhead[u];
	Qhead[u]=tot;
}
int father[Size];
bool vis[Size];
int Find(int x) {
	if(x==father[x])	return x;
	return father[x]=Find(father[x]);
}
void dfs(int x,int fa) {
	vis[x]=true;
	father[x]=x;
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		if(nxt!=fa) {
			dfs(nxt,x);
			father[nxt]=x;
		}
	}
	for(int i=Qhead[x]; i; i=Q[i].next) {
		int nxt=Q[i].v;
		if(vis[nxt]) {
			ans[Q[i].id]=Find(nxt);
		}
	}
}
int main() {
	n=read();
	m=read();
	s=read();
	for(re i=1; i<n; i++) {
		int u=read();
		int v=read();
		AddEdge(u,v);
		AddEdge(v,u);
	}
	for(re i=1; i<=m; i++) {
		int u=read();
		int v=read();
		AddQuery(u,v);
		AddQuery(v,u);
	}
	dfs(s,0);
	for(re i=1; i<=m; i++) {
		printf("%d\n",ans[i]);
	}
	return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值