板子 lca

这篇博客介绍了三种不同的算法来查找树中两点的最近公共祖先:Tarjan离线算法,倍增算法和RMQ(Range Minimum Query)结合ST表的方法。每种算法的时间复杂度分别为O(nlogn+q),O(nlogn)和O(n*log2(n)),查询时间为O(1)。这些方法都是针对离线查询的优化,并通过具体代码实现来说明了如何在实际问题中应用这些算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模板题

tarjan做法:

离线

总时间复杂度是O(nlogn+q)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=500010;
struct ty{
	ll t,next;
}edge[N<<1];
ll cnt1=0;
ll head[N];
ll n,m,s;
ll a,b;
template<typename Type>inline void read(Type &xx)
{
    Type f=1;char ch;xx=0;
    for(ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())xx=xx*10+ch-'0';
    xx*=f;
}
struct ty2//询问结构体 
{
	ll t,next;
	ll same;//会有一组与其相同的询问,
	//每次遍历到一组时要同时把另一组去掉
	ll num;//询问的编号,最后要按顺序输出
	bool flag=0;//标记该询问是否已经被回答过	 
}q[N<<1];
void addedge(ll a,ll b)
{
	edge[++cnt1].t=b;
	edge[cnt1].next=head[a];
	head[a]=cnt1;
}
ll cnt2=0;
ll head2[N];
ll fa[N];//并查集
void addque(ll a,ll b,ll c/*记录序号*/)
{
	q[++cnt2].t=b;
	q[cnt2].next=head2[a];
	head2[a]=cnt2;
	q[cnt2].same=cnt2+1;
	q[cnt2].num=c;
	q[++cnt2].t=a;
	q[cnt2].next=head2[b];
	head2[b]=cnt2;
	q[cnt2].num=c;
	q[cnt2].same=cnt2-1;
}
ll find(ll x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
} 
void comb(ll x,ll y)
{
	fa[find(x)]=find(y);//此处只要找到其父亲,不需要找到其祖先 
 } 
ll vis[N];//标记该节点是否被访问过 
ll ans[N];//记录答案 
void lca(ll p,ll fa)
{
	vis[p]=1;
	for(int i=head[p];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(y==fa) continue;
		if(vis[y]) continue;
		lca(y,p);
		comb(y,p);
	}
	for(int i=head2[p];i!=-1;i=q[i].next)
	{
		ll y=q[i].t;
		if(!vis[y]) continue;//这必须是一个被访问过的节点,不然没有信息可以做 
	    if(q[i].flag) continue;//代表这个询问已经做过了 
	    ans[q[i].num]=find(y);//记录答案 
	    q[i].flag=1;
	    q[q[i].same].flag=1;
	}
}

int main()
{//ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	read(n);read(m);read(s);
	memset(head,-1,sizeof head);
	memset(head2,-1,sizeof head2);
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=n-1;++i)
	{
		read(a);read(b);
		addedge(a,b);
		addedge(b,a);
	} //建树;
	for(int i=1;i<=m;++i)//离线建询问 
	{
		read(a);read(b);
		addque(a,b,i);
	}
	lca(s,-1);
	for(int i=1;i<=m;++i)
	{
		printf("%lld\n",ans[i]);
	}
	return 0;
}

倍增:

将所查的两个点先放到同一深度,再一起向上走直到相见

但是维护每一个节点上面的每一级的祖先是做不到的,空间扛不住,所以采用倍增思路。

时间复杂度为O(nlogn)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=500010;
struct ty{
	ll t,next;
}edge[N<<1];
ll cnt1=0;
ll head[N];
ll n,m,s;
ll a,b;
template<typename Type>inline void read(Type &xx)
{
    Type f=1;char ch;xx=0;
    for(ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())xx=xx*10+ch-'0';
    xx*=f;
}
void add(ll a,ll b)
{
	edge[++cnt1].t=b;
	edge[cnt1].next=head[a];
	head[a]=cnt1;
}
ll lg[N];
ll fa[N][22];
ll depth[N];
void dfs(ll u,ll p)
{
	fa[u][0]=p;
	depth[u]=depth[p]+1;
	for(int i=1;i<+lg[depth[u]];++i)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(y==p) continue;
		dfs(y,u);
	} 
}
ll lca(ll x,ll y)
{
	if(depth[x]<depth[y]) swap(x,y);
	while(depth[x]>depth[y])
	{
		x=fa[x][lg[depth[x]-depth[y]]-1];
	}
	if(x==y) return x;
	for(int i=lg[depth[x]]-1;i>=0;--i)
	{
		if(fa[x][i]!=fa[y][i])
		{
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	return fa[x][0];
}
int main()
{//ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	read(n);read(m);read(s);
	memset(head,-1,sizeof head);
	for(int i=1;i<=n-1;++i)
	{
		read(a);read(b);
		add(a,b);
		add(b,a);
	} //建树;
	for(int i=1;i<=n;++i)
	{
		lg[i]=lg[i-1]+((1<<lg[i-1])==i);
	}
	dfs(s,0);
	for(int i=1;i<=m;++i)
	{
		read(a);read(b);
		printf("%lld\n",lca(a,b));
	}
	return 0;
}

RMQ:

ST表+dfs序,将树掰成线性表来处理

还是要注意ST表的边界处理

时间复杂度O(n*log2(n)),查询O(1)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e6+10;
ll first[N];//每一个字符第一次出现的时候
template<typename Type>inline void read(Type &xx)
{
    Type f=1;char ch;xx=0;
    for(ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())xx=xx*10+ch-'0';
    xx*=f;
}
struct ty{
	ll t,next; 
}edge[N];
ll cnt=0;
ll head[N];
void add(ll a,ll b)
{
	edge[++cnt].t=b;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
ll ver[N];//dfs第几个访问的节点 
ll r[N]; //节点的深度
ll tot=0;//访问的序号 
void dfs(ll u,ll dep)
{
    first[u]=++tot;ver[tot]=u;r[tot]=dep;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
    	ll y=edge[i].t;
    	if(first[y]) continue;/////////搜过就跳过 
    	dfs(y,dep+1);
    	ver[++tot]=u;r[tot]=dep;
	}
} 
ll n,m,s,a,b;
ll lg[N];
void init()
{
	lg[1]=0;
	lg[2]=1;
	for(int i=3;i<=N;++i) lg[i]=lg[i/2]+1;
}
ll f[20][N],rec[20][N];
//rec记录的是区间内深度最小值的编号
int main()
{
	memset(head,-1,sizeof head);
	init();
	read(n);read(m);read(s);
	for(int i=1;i<=n-1;++i)
	{
		cin>>a>>b;
		add(a,b);
		add(b,a);
	}
	dfs(s,1);//第二个参数是深度,不是爸爸,所以写1而不是-1 
	for(int i=1;i<=tot;++i)
	{
	    f[0][i]=r[i];
		rec[0][i]=ver[i];//分别记录数字内容和对应的深度	
	} 
	for(int i=1;i<=lg[tot];++i)
	{
		for(int j=1;j+(1<<i)-1<=tot;++j)
		{
			if(f[i-1][j]<f[i-1][j+(1<<(i-1))])
			{
				f[i][j]=f[i-1][j];
				rec[i][j]=rec[i-1][j];
			}
			else
			{
				f[i][j]=f[i-1][j+(1<<(i-1))];
				rec[i][j]=rec[i-1][j+(1<<(i-1))];
			}
		}
	}
	ll l,r;
	for(int i=1;i<=m;++i)
	{
		read(l);read(r);
		l=first[l];r=first[r];
		if(l>r) swap(l,r);
		ll k=lg[r-l+1];
//		ll k=0;
//		while((1<<k)<=r-l+1) k++;
//		k--;
		if(f[k][l]<f[k][r-(1<<k)+1]) cout<<rec[k][l]<<endl;
		else cout<<rec[k][r-(1<<k)+1]<<endl;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值