LCA 最近公共祖先 CodeForces Minimum spanning tree for each edge

LCA基础思想

给定一个树/无向图,再给定若干个询问,询问内容是给出两点,要求给出这两点的最近公共祖先。
这样子就像两个点各有一条路径回溯到根节点,求这两条路径第一次相交的点在哪里
所以最基础的lca,我们就可以找到两条路径,然后找出那个LCA,具体操作就是在一棵树内,标出每个点的深度,然后先把低的那个点跳到高处,然后两个点重合就说明找到LCA,如果不同就一同往上跳,直到找到相同的爹。

LCA二进制跳表

刚刚那个时间复杂度是O(n^2),所以挺鸡肋的,我们就要用到伟大的二进制,我们知道对于任意一个整数n,它都可以找到唯一的一组x1,x2,x3,…来满足一下等式:

n=2^x1+ 2^x2 +2^x3+…

所以对于任意一个整数,都能通过按2的次幂跳来实现。
这里给出超多解析的代码

例题:CF Minimum spanning tree for each edge

E. Minimum spanning tree for each edge

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=200000;
const int MAXM=200000;
int n,m,logn;
long long Min;
int fa[MAXN+5],bi[MAXM+5],dep[MAXN+5];
int lca[MAXN+5][25],lcar[MAXN+5][25];
struct node
{
	int x,y,z,t;
}
s[MAXM+5];
bool cmp1(node A,node B)
{
	return A.z<B.z;
}
bool cmp2(node A,node B)
{
	return A.t<B.t;
}
vector<int>T[MAXN+5],P[MAXN+5];
int find(int x)
{
	if(x==fa[x])
		return x;
	return
		fa[x]=find(fa[x]);
}
void Add(int x,int y,int z)
{
	T[x].push_back(z);  //子树权值 
	P[x].push_back(y);  //子树 
}
void Init_Find() //生成最小生成树
{ 
	sort(s+1,s+m+1,cmp1);
	for(int i=1; i<=n; i++)
		fa[i]=i;
	for(int i=1,j=1; i<=m&&j<n; i++)
	{
		int a=find(s[i].x);
		int b=find(s[i].y);
		if(a!=b) 
		{
			fa[a]=b;
			j++;
			Min+=s[i].z;
			bi[s[i].t]=1;
			Add(s[i].x,s[i].y,s[i].z);
			Add(s[i].y,s[i].x,s[i].z);
		}
	}
	sort(s+1,s+m+1,cmp2);  // 排回原来的序 
}
void Init_dfs(int u,int fa) //初始化第i个节点的父亲及连接边权
{ 
	lca[u][0]=fa;  //u 是fa 的直接儿子   所以向上跳2^0=1; 
	for(int i=0; i<P[u].size(); i++)  //遍历当前节点的所有子节点 
	{
		int v=P[u][i];
		if(v==fa) // 当前节点的子节点是他爹 
		{
			lcar[u][0]=T[u][i];//u节点到父亲节点的边权记录 
			continue;
		}
		dep[v]=dep[u]+1; //v 是u的儿子 
		Init_dfs(v,u);
	}
}
void Init_LCA()//初始化完第i个节点的第2^j个祖先,及沿途最大边权
{ 
	for(int i=1; (1<<i)<=n; i++) //遍历每一种深度表达的每一个二进制位 
	{
		for(int j=1; j<=n; j++)   
			if(lca[j][i-1])  //如果j跳2^(i-1)后有爹 
			{
				lca[j][i]=lca[lca[j][i-1]][i-1]; //那么j跳2^(i)层的爹就是 j跳2^(i-1)的爹再跳2^(i-1) 
				lcar[j][i]=lcar[lca[j][i-1]][i-1];
				lcar[j][i]=max(lcar[j][i],lcar[j][i-1]);
			}
	}
		
}
int LCA_RET(int x,int y)//查找x,y节点至其LCA路径上的最大边权
{ 
	int ret=0;
	if(dep[x]<dep[y])//固定x比y深
		swap(x,y);
	int de=dep[x]-dep[y];
	for(int i=logn; i>=0; i--) //二进制上升,使x,y同一高度
		if(de>>i&1)
		{
			ret=max(ret,lcar[x][i]);
			x=lca[x][i];
		}
	//printf("%d %d ",dep[x],dep[y]);
	if(x==y)//若y为LCA(即x,y在同一条链上)
		return ret;
	for(int i=logn; i>=0; i--) //不断上升同一高度
		if(lca[x][i]!=lca[y][i])
		{
			ret=max(ret,max(lcar[x][i],lcar[y][i]));
			x=lca[x][i];
			y=lca[y][i];
		}
	ret=max(ret,max(lcar[x][0],lcar[y][0]));
	return ret;
}
int main() 
{
	//freopen("span.in","r",stdin);
	//freopen("span.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++) 
	{
		scanf("%d",&s[i].x);
		scanf("%d",&s[i].y);
		scanf("%d",&s[i].z);
		s[i].t=i;
	}
	logn=20;
	Init_Find();
	Init_dfs(1,0);
	Init_LCA();
	for(int i=1; i<=m; i++) 
	{
		if(bi[i])
			printf("%lld\n",Min);
		else 
		{
			int tmp=LCA_RET(s[i].x,s[i].y);
			printf("%lld\n",Min-tmp+s[i].z);
		}
	}
	return 0;
}

提炼模板

struct node
{
	int x,y,z,t;
}
s[MAXM+5];
bool cmp1(node A,node B)
{
	return A.z<B.z;
}
bool cmp2(node A,node B)
{
	return A.t<B.t;
}
vector<int>T[MAXN+5],P[MAXN+5];
int find(int x)
{
	if(x==fa[x])
		return x;
	return
		fa[x]=find(fa[x]);
}
void Add(int x,int y,int z)
{
	T[x].push_back(z);  //子树权值 
	P[x].push_back(y);  //子树 
}
void Init_Find() //生成最小生成树
{ 
	sort(s+1,s+m+1,cmp1);
	for(int i=1; i<=n; i++)
		fa[i]=i;
	for(int i=1,j=1; i<=m&&j<n; i++)
	{
		int a=find(s[i].x);
		int b=find(s[i].y);
		if(a!=b) 
		{
			fa[a]=b;
			j++;
			Min+=s[i].z;
			bi[s[i].t]=1;
			Add(s[i].x,s[i].y,s[i].z);
			Add(s[i].y,s[i].x,s[i].z);
		}
	}
	sort(s+1,s+m+1,cmp2);  // 排回原来的序 
}
void Init_dfs(int u,int fa) //初始化第i个节点的父亲及连接边权
{ 
	lca[u][0]=fa;  //u 是fa 的直接儿子   所以向上跳2^0=1; 
	for(int i=0; i<P[u].size(); i++)  //遍历当前节点的所有子节点 
	{
		int v=P[u][i];
		if(v==fa) // 当前节点的子节点是他爹 
		{
			lcar[u][0]=T[u][i];//u节点到父亲节点的边权记录 
			continue;
		}
		dep[v]=dep[u]+1; //v 是u的儿子 
		Init_dfs(v,u);
	}
}
void Init_LCA()//初始化完第i个节点的第2^j个祖先,及沿途最大边权
{ 
	for(int i=1; (1<<i)<=n; i++) //遍历每一种深度表达的每一个二进制位 
	{
		for(int j=1; j<=n; j++)   
			if(lca[j][i-1])  //如果j跳2^(i-1)后有爹 
			{
				lca[j][i]=lca[lca[j][i-1]][i-1]; //那么j跳2^(i)层的爹就是 j跳2^(i-1)的爹再跳2^(i-1) 
				lcar[j][i]=lcar[lca[j][i-1]][i-1];
				lcar[j][i]=max(lcar[j][i],lcar[j][i-1]);
			}
	}
		
}
int LCA_RET(int x,int y)//查找x,y节点至其LCA路径上的最大边权
{ 
	int ret=0;
	if(dep[x]<dep[y])//固定x比y深
		swap(x,y);
	int de=dep[x]-dep[y];
	for(int i=logn; i>=0; i--) //二进制上升,使x,y同一高度
		if(de>>i&1)
		{
			ret=max(ret,lcar[x][i]);
			x=lca[x][i];
		}
	//printf("%d %d ",dep[x],dep[y]);
	if(x==y)//若y为LCA(即x,y在同一条链上)
		return ret;
	for(int i=logn; i>=0; i--) //不断上升同一高度
		if(lca[x][i]!=lca[y][i])
		{
			ret=max(ret,max(lcar[x][i],lcar[y][i]));
			x=lca[x][i];
			y=lca[y][i];
		}
	ret=max(ret,max(lcar[x][0],lcar[y][0]));
	return ret;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值