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;
}