LCA-最近公共祖先
两个点在树上距离最近的公共祖先节点
lca有主要的两种算法
1.tarjan:离线算法,复杂度O(n+q)
2.倍增|RMQ:在线算法
1.在线算法:倍增法
倍增O(nlogn)查询
怎么求lca?
1.先将深度大的移动到小的一样深
2.然后同时尽往上跳,但没就是没有跳过lca点,最后会调到lca的两个子节点上
代码:
1.建树并预处理每个点的深度,每个点的第2^i个祖先的
2.求lca,从大到小枚举,让深度大的点一直往上跳,但一种>=(深度小的)
判断是否移动到同一点,如果同一点,直接返回
3.两个点在同一深度,这时就方便一起往上跳,但是就是不能跳到同一点,最后会跳到lca的两个子节点上
建树&&预处理:
void dfs(int x,int fa)//件树,预处理每个节点的深度与第2^i个祖先
{
grd[x][0]=fa;
down[x]=down[fa]+1;
for(int i=1;i<=20;i++)
grd[x][i]=grd[grd[x][i-1]][i-1];//预处理x的第2^i个祖先
for(int i=head[x];i;i=next1[i])
{
int v=to[i];
if(v!=fa)
dfs(v,x);
}
}
lca部分代码:
因为预处理出的是第2^i个祖先,所以满足单调性,可以从大到小直接枚举
int lca(int x,int y)
{
if(down[x]<down[y])
swap(x,y);
for(int i=20;i>=0;i--)//x一直往上跳,但x的深度一直>=y的深度
{
if(down[x]-(1<<i)>=y)
x=grd[x][i];
}
if(x==y)//y是x的祖先
return x;
for(int i=20;i>=0;i--)//一起往上跳,但是就是不能跳到同一点,最后会跳到lca的两个子节点上
{ //i很大当然是同一祖先,直接从大到小枚举
if(grd[x][i]!=grd[y][i])
x=grd[x][i],y=grd[y][i];
}
return grd[x][0];
}
模板题:https://www.luogu.org/problem/P3379
洛谷p3389,求lca点
#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
int to[MAXN<<1],next1[MAXN<<1],head[MAXN<<1];
int tot=0;
int down[MAXN];
int grd[MAXN][33];
void add(int u,int v)
{
to[++tot]=v;
next1[tot]=head[u];
head[u]=tot;
}
void dfs(int x,int fa)//件树,预处理每个节点的深度与第2^i个祖先
{
grd[x][0]=fa;
down[x]=down[fa]+1;
for(int i=1;i<=20;i++)
grd[x][i]=grd[grd[x][i-1]][i-1];//预处理x的第2^i个祖先
for(int i=head[x];i;i=next1[i])
{
int v=to[i];
if(v!=fa)
dfs(v,x);
}
}
int lca(int x,int y)
{
if(down[x]<down[y])
swap(x,y);
for(int i=20;i>=0;i--)//x一直往上跳,但x的深度一直>=y的深度
{
if(down[x]-(1<<i)>=y)
x=grd[x][i];
}
if(x==y)//y是x的祖先
return x;
for(int i=20;i>=0;i--)//i很大当然是同一祖先,直接从大到小枚举
{
if(grd[x][i]!=grd[y][i])
x=grd[x][i],y=grd[y][i];
}
return grd[x][0];
}
int main()
{
int n,m,root,a,b;
scanf("%d%d%d",&n,&m,&root);
for(int i=1;i<=n-1;i++)
{
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
dfs(root,0);
while(m--)
{
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}
return 0;
}
感谢:https://blog.youkuaiyun.com/qq_42790311/article/details/81486742
求距离:
lca_tarjan算法:
#include<bits/stdc++.h>
#define pb push_back
#define MAXN 100005
#define ll long long
using namespace std;
int to[MAXN*2],next1[MAXN*2],val[2*MAXN],head[MAXN*2];
int f[MAXN];
int dis[MAXN];
int vis[MAXN];
int lca[MAXN];
int ans[MAXN];
struct node
{
int v,id;
};
vector<node> que[MAXN];
int tot,n;
void init()
{
tot=0;
for(int i=1;i<=n;++i)
{
head[i]=-1,f[i]=i,vis[i]=0;
que[i].clear();
}
}
void add(int x,int y,int z)
{
to[++tot]=y;
val[tot]=z;
next1[tot]=head[x];
head[x]=tot;
}
void add_que(int a,int b,int id)
{
que[a].pb({b,id});
}
int get(int x)
{
if(f[x]!=x)
f[x]=get(f[x]);
return f[x];
}
void tarjan(int x)
{
vis[x]=1;
for(int i=head[x];i!=-1;i=next1[i])
{
int v=to[i];
if(vis[v])
continue;
dis[v]=dis[x]+val[i];
tarjan(v);
f[v]=x;
}
int sz=que[x].size();
for(int i=0;i<sz;i++)
{
int v=que[x][i].v,id=que[x][i].id;
if(vis[v]==2)
{
int lcav=get(v);
ans[id]=min(ans[id],dis[x]+dis[v]-2*dis[lcav]);
}
}
vis[x]=2;
}
int main()
{
int t,m,a,b,c;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init();
for(int i=1;i<n;++i)
{
scanf("%d%d%d",&a,&b,&c);
if(a>b)
swap(a,b);
add(a,b,c);
}
for(int i=1;i<=m;++i)
{
scanf("%d%d",&a,&b);
if(a>b)
swap(a,b);
if(a==b)
{
ans[i]=0;
}
else
{
add_que(a,b,i);
ans[i]=1<<30;
}
}
tarjan(1);
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
}
}
lca_rmq:
#include <bits/stdc++.h>
using namespace std;
const int MAXN=200005;
int n,m,tot,time1;
int to[MAXN],val[MAXN],next1[MAXN],head[MAXN];
int deep[MAXN];
int in[MAXN],out[MAXN];
int ST[MAXN*2][20];
void init()
{
time1=tot=0;
memset(head,-1,sizeof(head));
}
void add(int u,int v,int c)
{
to[++tot]=v;
val[tot]=c;
next1[tot]=head[u];
head[u]=tot;
}
void dfs(int x,int pre)
{
in[x]=++time1;
ST[time1][0]=x;
for (int i=head[x];i!=-1;i=next1[i])
{
if (to[i]!=pre)
{
deep[to[i]]=deep[x]+val[i];
dfs(to[i],x);
ST[++time1][0]=x;
}
}
out[x]=time1;
}
void Get_ST(int n)
{
for (int i=1;i<=n;i++)
{
for (int j=1;j<20;j++)
{
ST[i][j]=ST[i][j-1];
int v=i-(1<<(j-1));
if (v>0&&deep[ST[v][j-1]]<deep[ST[i][j]])
ST[i][j]=ST[v][j-1];
}
}
}
int RMQ(int L,int R)
{
int val=floor(log(R-L+1)/log(2));
int x=ST[L+(1<<val)-1][val],y=ST[R][val];
return deep[x]<deep[y]?x:y;
}
int main()
{
int t,a,b,c;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
dfs(1,0);
deep[0]=1e9;
Get_ST(time1);
while (m--)
{
scanf("%d%d",&a,&b);
if (in[a]>in[b])
swap(a,b);
int LCA=RMQ(in[a],in[b]);
printf("%d\n",deep[a]+deep[b]-deep[LCA]*2);
}
}
return 0;
}
树上第k小
https://vjudge.net/problem/SPOJ-COT
题意:
求树上两点间路径第k小
解析:
数组上建主席树是root[i]在root[i-1]的基础上建链
树上建主席树是root[i]在root[fa[i]]的基础上建立链
树上两点间的路径有且仅有一条,且必过lca点,我们需要求lca
如果是求(a,b)的距离,呢么d(x,y)=d(1,x)+d(1,y)-2*d(1,lca);
但是这里不行,不能减去两个lca,x和y都算过包含1次lca,最后也要包含一次lca,所以最后减去的是fa(lca).要保留一个lca点
ac:
#include<bits/stdc++.h>
#define MAX 100005
#define MAXN 3000005
using namespace std;
int a[MAXN],b[MAXN],root[MAXN];
int L[MAXN],R[MAXN];
int sum[MAXN];
int tot=0,num=0,cnt=0;
int to[MAX<<2],nxt[MAX<<2],head[MAX<<2];
int down[MAX<<2];
int grd[100005][23];
void add(int u,int v)
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
int build(int l,int r)
{
int now=+tot;
sum[now]=0;
if(l!=r)
{
int mid=(l+r)>>1;
L[now]=build(l,mid);
R[now]=build(mid+1,r);
}
return now;
}
int update(int pre,int l,int r,int k)
{
int now=++tot;
sum[now]=sum[pre]+1;
L[now]=L[pre],R[now]=R[pre];
if(l!=r)
{
int mid=(l+r)>>1;
if(k<=mid)
L[now]=update(L[pre],l,mid,k);
else
R[now]=update(R[pre],mid+1,r,k);
}
return now;
}
int query(int pre,int now,int lca,int flca,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)>>1;
int lcnt=sum[L[now]]+sum[L[pre]]-sum[L[lca]]-sum[L[flca]];
if(k<=lcnt)
return query(L[pre],L[now],L[lca],L[flca],l,mid,k);
else
return query(R[pre],R[now],R[lca],R[flca],mid+1,r,k-lcnt);
}
void dfs(int x,int fa)
{
down[x]=down[fa]+1;
grd[x][0]=fa;
for(int i=1;i<=20;i++)//预处理倍增祖先
grd[x][i]=grd[grd[x][i-1]][i-1];
root[x]=update(root[fa],1,num,a[x]);//边dfs,边建链
for(int i=head[x];i;i=nxt[i])
{
int v=to[i];
if(down[v]==0)
dfs(v,x);
}
}
int LCA(int x,int y)//求lca
{
if(down[x]<down[y])
swap(x,y);
for(int i=20;i>=0;i--)
if(down[x]-(1<<i)>=down[y])
x=grd[x][i];
if(x==y)
return y;
for(int i=20;i>=0;i--)
if(grd[x][i]!=grd[y][i])
x=grd[x][i],y=grd[y][i];
return grd[x][0];
}
void init()
{
cnt=tot=num=0;
memset(down,0,sizeof(down));
memset(head,0,sizeof(head));
memset(grd,0,sizeof(grd));
}
int main()
{
int n,m,u,v,k;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
num=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+1+num,a[i])-b;
for(int i=1;i<=n-1;i++)
scanf("%d%d",&u,&v),add(u,v),add(v,u);
root[0]=build(1,num);
down[0]=0;
dfs(1,0);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&k);
int lca=LCA(u,v);
printf("%d\n",b[query(root[u],root[v],root[lca],root[grd[lca][0]],1,num,k)]);//ans=u+v-lca-flca,(保留一个lca点)
}
}
return 0;
}