lca模板总结

lca模板总结


倍增:初始化O(nlogn),查询O(logn)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int D = 25;
const int maxn = 500000 + 10;
int k,head[maxn],d[maxn],f[maxn][D + 5];
struct node{
	int v,nxt;
}e[maxn << 1];
void adde(int u,int v){
	e[k].v = v;
	e[k].nxt = head[u];
	head[u] = k++;
}
void dfs(int now,int fa)
{
	d[now] = d[fa] + 1;	f[now][0] = fa;
	for(int i = head[now]; i != -1; i = e[i].nxt)
		if(e[i].v != fa)
			dfs(e[i].v,now);
}
int lca(int u,int v)
{
	if(d[u] < d[v]) swap(u,v);
	for(int i = D; i >= 0; --i)
		if(d[v] <= d[f[u][i]]) u = f[u][i];
	if(u == v) return u;
	for(int i = D; i >= 0; --i)
		if(f[u][i] != f[v][i])
		{
			u = f[u][i];
			v = f[v][i];
		}
	return f[u][0];	
}
int main(){
	int n,m,s,a,b;
	memset(head,-1,sizeof(head));
	scanf("%d%d%d",&n,&m,&s);
	for(int i = 1; i < n; ++i){
		scanf("%d%d",&a,&b);
		adde(a,b);adde(b,a);
	}
	dfs(s,0);//!!
	for(int j = 1; j <= D; ++j)
		for(int i = 1; i <= n; ++i)
			f[i][j] = f[f[i][j - 1]][j - 1];//!!
	while(m--){
		scanf("%d%d",&a,&b);
		printf("%d\n",lca(a,b));
	}
	return 0;
}

tarjan:O(n+q)

1.任选一个点为根节点,从根节点开始。

2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。

3.若是v还有子节点,返回2,否则下一步。

4.合并v到u上。

5.寻找与当前点u有询问关系的点v。

6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。

Tarjan(u)//marge和find为并查集合并函数和查找函数
{
    for each(u,v)    //访问所有u子节点v
    {
        Tarjan(v);        //继续往下遍历
        marge(u,v);    //合并v到u上
        标记v被访问过;
    }
    for each(u,e)    //访问所有和u有询问关系的e
    {
        如果e被访问过
        u,e的最近公共祖先为find(e);//只能是find(e)!!
    }
}
struct node{
	int v,nxt;
}e[maxn << 1];
struct query{
	int v,w,nxt;
}q[maxn << 1];
void Adde(int u,int v){
	e[k].v = v;
	e[k].nxt = head[u];
	head[u] = k++;
}
void Addq(int u,int v){
	q[cnt].v = v;
	q[cnt].nxt = headq[u];
	headq[u] = cnt++;
}
int Find(int u){
	return (fa[u] == u) ? u : fa[u] = Find(fa[u]);
}
void Tarjan(int u){
	for(int i = head[u]; i != -1; i = e[i].nxt){
		if(!vis[e[i].v]){
			vis[e[i].v] = 1;
			Tarjan(e[i].v);
			fa[e[i].v] = u;
		}
	}
	for(int i = headq[u]; i != -1; i = q[i].nxt){
		if(vis[q[i].v]){
			q[i].w = q[i ^ 1].w = Find(q[i].v);
		}
	}
}
int main(){
	int n,m,s,a,b;
	memset(head,-1,sizeof(head));
	memset(headq,-1,sizeof(headq));
	n = read();m = read();s = read();
	for(int i = 1; i <= n; ++i) fa[i] = i;
	for(int i = 1; i < n; ++i){
		a = read();b = read();
		Adde(a,b);Adde(b,a);
	}
	for(int i = 1; i <= m; ++i){
		a = read();b = read();
		Addq(a,b);Addq(b,a);
	}
	vis[s] = 1;//!!
	Tarjan(s);
	for(int i = 0;i < cnt; i += 2){
		printf("%d\n",q[i].w);
	}
	return 0;
}

ST表.初始化O(nlogn),查询O(1)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int read(){
	char ch;int op=1;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') op=-1;
	int ret=ch-'0';
	while((ch=getchar())>='0'&&ch<='9') ret=ret*10+ch-'0';
	return ret*op;
}
const int N=1500000+10;
const int D=20;
double lg2;
int n,m,kk,idx,s;
int head[N],depth[N],euler[N],first[N],dist[N],ST[N][22];
struct edge{
	int v,nxt;
}e[N<<1];
void adde(int u,int v){
	e[kk].v=v;
	e[kk].nxt=head[u];
	head[u]=kk++;
}
int calc(int x,int y){
	return depth[x]<depth[y]?x:y;
}
void dfs(int u,int fa,int d){
	euler[++idx]=u;depth[idx]=d;first[u]=idx;
	for(int i=head[u];i!=-1;i=e[i].nxt){
		int v=e[i].v;
		if(v!=fa){
			dfs(v,u,d+1);
			euler[++idx]=u;depth[idx]=d;
		}
	}
}
void init(int root){
	idx=0;dist[root]=0;
	dfs(root,0,1);
	for(int i=1;i<=idx;++i) ST[i][0]=i;
	for(int j=1;(1<<j)<=idx;++j)
		for(int i=1;i+(1<<j)-1<=idx;++i)
			ST[i][j]=calc(ST[i][j-1],ST[i+(1<<(j-1))][j-1]);
}
int Lca(int x,int y){
	int l=first[x],r=first[y];
	if(l>r) swap(l,r);
	int k=log(r-l+1)/log(2);
	return euler[calc(ST[l][k],ST[r-(1<<k)+1][k])];
} 

int main(){
	int a,b;
	lg2=log(2);
	n=read();m=read();s=read();
	memset(head,-1,sizeof(head));
	for(int i=1;i<n;++i){
		a=read();b=read();
		adde(a,b);adde(b,a);
	}
	init(s);
	while(m--){
		a=read();b=read();
		printf("%d\n",Lca(a,b));
	}
	return 0;
}

树链剖分求lca

详见树剖总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值