LCA 最近公共祖先
在一棵沒有環(huán)的樹上,每個(gè)節(jié)點(diǎn)肯定有其父親節(jié)點(diǎn)和祖先節(jié)點(diǎn)震嫉,而最近公共祖先赤赊,就是兩個(gè)節(jié)點(diǎn)在這棵樹上深度最大的公共的祖先節(jié)點(diǎn)。
換句話說聚请,就是兩個(gè)點(diǎn)在這棵樹上距離最近的公共祖先節(jié)點(diǎn)荠雕。
對(duì)于一棵有權(quán)樹,假設(shè)u,v兩點(diǎn)的最近公共祖先lca(v),若dis[u]表示根節(jié)點(diǎn)到u的長(zhǎng)度驶赏, 則uv間的距離為dis=dis[u]+dis[v]-2*dis[lca(v)]
有人可能會(huì)問:那他本身或者其父親節(jié)點(diǎn)是否可以作為祖先節(jié)點(diǎn)呢?
答案是肯定的既鞠,很簡(jiǎn)單煤傍,按照人的親戚觀念來說,你的父親也是你的祖先嘱蛋,而LCA還可以將自己視為祖先節(jié)點(diǎn)蚯姆。
舉個(gè)例子吧,如下圖所示4和5的最近公共祖先是2洒敏,5和3的最近公共祖先是1龄恋,2和1的最近公共祖先是1⌒谆铮
求LCA主要有兩種方法:離線的DFS+并查集和在線的RMQ算法
這里簡(jiǎn)單說一說在線和離線的區(qū)別:在計(jì)算機(jī)科學(xué)中郭毕,一個(gè)在線算法是指它可以以序列化的方式一個(gè)個(gè)的處理輸入,也就是說在開始時(shí)并不需要已經(jīng)知道所有的輸入函荣。相對(duì)的显押,對(duì)于一個(gè)離線算法,在開始時(shí)就需要知道問題的所有輸入數(shù)據(jù)傻挂,而且在解決一個(gè)問題后就要立即輸出結(jié)果乘碑。
function TarjanOLCA(u)
u.ancestor := u;
for each v in u.children do
TarjanOLCA(v);
Union(u,v);
Find(u).ancestor := u;
u.color := black;
for each v such that {u,v} in P do
if v.color == black
print "Tarjan's Lowest Common Ancestor of " + u +
" and " + v + " is " + Find(v).ancestor + ".";
其中,根節(jié)點(diǎn)是可以隨意選擇的
How far away ?
題意:
給定一棵帶權(quán)樹金拒,求u兽肤,v兩節(jié)點(diǎn)的路徑長(zhǎng)度
題解:
找到u,v兩點(diǎn)的最近公共祖先lca(v)
若dis[u]表示根節(jié)點(diǎn)到u的長(zhǎng)度,則dis=dis[u]+dis[v]-2*dis[lca(v)]
#include<cstdio>
#include <algorithm>
#include<cstring>
using namespace std;
const int MAXN=40010;
const int BLACK=1,WHITE=0;
int father[MAXN],vis[MAXN],dis[MAXN],color[MAXN];
int parent(int u)
{
while(u!=father[u])
{
father[u]=father[father[u]];
u=father[u];
}
return u;
}
void connect(int u,int v)//v作為u的兒子
{
int fu=parent(u);
int fv=parent(v);
father[fv]=fu;
}
struct Node
{
int to,next,val;
};
Node edge[MAXN<<1];
int head[MAXN],cnt;
void addEdge(int u,int v,int val)
{
edge[cnt].to=v;
edge[cnt].val=val;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].to=u;
edge[cnt].val=val;
edge[cnt].next=head[v];
head[v]=cnt++;
}
struct Ask
{
int u,to,next,lca;
};
Ask arr[MAXN<<1];
int ask_head[MAXN],idx;
void addAsk(int u,int v)
{
arr[idx].u=u;arr[idx].to=v;arr[idx].next=ask_head[u];ask_head[u]=idx++;
arr[idx].u=v;arr[idx].to=u;arr[idx].next=ask_head[v];ask_head[v]=idx++;
}
void tarjan(int u)
{
vis[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]==0)
{
dis[v]=dis[u]+edge[i].val;
tarjan(v);
connect(u,v);
}
}
color[u]=BLACK;
for(int i=ask_head[u];i!=-1;i=arr[i].next)
{
int v=arr[i].to;
if(color[v]==BLACK)
{
int fa=parent(v);
arr[i].lca=fa;
arr[i^1].lca=fa;
}
}
}
int main()
{
int t,n,m,u,v,val;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
memset(ask_head,-1,sizeof(ask_head));
memset(vis,0,sizeof(vis));
memset(color,WHITE,sizeof(color));
for(int i=1;i<=n;i++) father[i]=i;
cnt=idx=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&val);
addEdge(u,v,val);
}
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
addAsk(u,v);
}
dis[1]=0;
tarjan(1);
for(int i=0;i<idx;i+=2)
{
printf("%d\n",dis[arr[i].u]+dis[arr[i].to]-2*dis[arr[i].lca]);
}
}
}
( 二 )在線算法: RMQ算法
一:LCA和RMQ是可以相互轉(zhuǎn)化的
LCA轉(zhuǎn)RMQ算法是一個(gè)在線算法:先用時(shí)間去做預(yù)處理,然后每讀入一個(gè)詢問资铡,就用很短的時(shí)間去回答它沉迹,即”問一個(gè)答一個(gè),回答時(shí)間很短“
預(yù)備知識(shí):LCA轉(zhuǎn)為RMQ后害驹,幾乎是裸的RMQ問題鞭呕,RMQ問題,這里推薦ST算法求解宛官,如果不懂ST算法葫松,先學(xué)習(xí)一下
RMQ算法求LCA的模板如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=500010;
const int MAXE=500010;
struct Node
{
int to,next,val;
};
Node edge[MAXE];
int head[MAXN],cnt;
void addEdge(int u,int v,int val)
{
edge[cnt].to=v;
edge[cnt].val=val;
edge[cnt].next=head[u];
head[u]=cnt++;
}
int sequence[MAXN*2];//保存遍歷的節(jié)點(diǎn)序列,長(zhǎng)度為2*n-1,從下標(biāo)1開始保存
int deep[MAXN*2];//和遍歷序列對(duì)應(yīng)的深度數(shù)組,長(zhǎng)度為2*n-1,從下標(biāo)1開始保存
int first[MAXN];//每個(gè)節(jié)點(diǎn)在遍歷序列時(shí)第一次出現(xiàn)的位置
int dis[MAXN];//每個(gè)節(jié)點(diǎn)到樹根的距離
int dp[MAXN*1][25];//Sparse_table
bool vis[MAXN];//DFS遍歷的訪問數(shù)組
int tot;//序列下標(biāo)記錄
void DFS(int u,int dep)
{
vis[u]=1;
sequence[++tot]=u;first[u]=tot;deep[tot]=dep;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]==0)
{
dis[v]=dis[u]+edge[i].val;
DFS(v,dep+1);
sequence[++tot]=u;
deep[tot]=dep;
}
}
}
void ST(int len)//index from 1 - len
{
for(int i=1;i<=len;i++) dp[i][0]=i;
for(int j=1;(1<<j)<=len;j++)//(length)=j<<1
for(int i=1;i+(1<<j)-1<=len;i++)
{
int a=dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
if(deep[a]<deep[b]) dp[i][j]=a;
else dp[i][j]=b;
}
}
int RMQ(int x,int y)
{
int k=0;
while(1<<(k+1)<=y-x+1) k++;
int a=dp[x][k],b=dp[y-(1<<k)+1][k];
if(deep[a]<deep[b]) return a;
else return b;
}
int LCA(int a,int b)
{
int x=first[a],y=first[b];
if(x>y) swap(x,y);
return sequence[RMQ(x,y)];
}
int main()
{
int t,n,m,u,v,val;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
cnt=tot=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&val);
addEdge(u,v,val);
addEdge(v,u,val);
}
dis[1]=0;
DFS(1,1);
ST(tot);
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
printf("%d\n",dis[u]+dis[v]-2*dis[LCA(u,v)]);
}
}
}
類似的,這里給出帶有pos數(shù)組的RMQ算法
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=40010;
const int MAXE=MAXN*2;
struct Node
{
int to,next,val;
};
Node edge[MAXE];
int head[MAXN],cnt;
void addEdge(int u,int v,int val)
{
edge[cnt].to=v;
edge[cnt].val=val;
edge[cnt].next=head[u];
head[u]=cnt++;
}
int sequence[MAXN*2];//保存遍歷的節(jié)點(diǎn)序列,長(zhǎng)度為2*n-1,從下標(biāo)1開始保存
int deep[MAXN*2];//和遍歷序列對(duì)應(yīng)的深度數(shù)組,長(zhǎng)度為2*n-1,從下標(biāo)1開始保存
int first[MAXN];//每個(gè)節(jié)點(diǎn)在遍歷序列時(shí)第一次出現(xiàn)的位置
int dis[MAXN];//每個(gè)節(jié)點(diǎn)到樹根的距離
int dp[MAXN*2][25];//Sparse_table
int pos[MAXN*2][25];
bool vis[MAXN];//DFS遍歷的訪問數(shù)組
int tot;//序列下標(biāo)記錄
int temp[MAXN*2];
void DFS(int u,int dep)
{
vis[u]=1;
sequence[++tot]=u;first[u]=tot;deep[tot]=dep;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]==0)
{
dis[v]=dis[u]+edge[i].val;
DFS(v,dep+1);
sequence[++tot]=u;
deep[tot]=dep;
}
}
}
void ST(int len)//index from 1 - len
{
for(int i=1;i<=len;i++) dp[i][0]=deep[i];
for(int i=1;i<=len;i++) pos[i][0]=i;
for(int j=1;(1<<j)<=len;j++)//(length)=j<<1
for(int i=1;i+(1<<j)-1<=len;i++)
{
if(dp[i][j-1]<dp[i+(1<<(j-1))][j-1])
{
dp[i][j]=dp[i][j-1];
pos[i][j]=pos[i][j-1];
}
else
{
dp[i][j]=dp[i+(1<<(j-1))][j-1];
pos[i][j]=pos[i+(1<<(j-1))][j-1];
}
}
}
int RMQ(int x,int y)
{
int k=0;
while(1<<(k+1)<=y-x+1) k++;
return dp[x][k]<dp[y-(1<<k)+1][k]?pos[x][k]:pos[y-(1<<k)+1][k];
}
int LCA(int a,int b)
{
int x=first[a],y=first[b];
if(x>y) swap(x,y);
return sequence[RMQ(x,y)];
}
int main()
{
int t,n,m,u,v,val;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
cnt=tot=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&val);
addEdge(u,v,val);
addEdge(v,u,val);
}
dis[1]=0;
DFS(1,1);
ST(tot);
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
printf("%d\n",dis[u]+dis[v]-2*dis[LCA(u,v)]);
}
}
}
Design the city
題意:
求樹的三點(diǎn)距離
題解:
u,v,w三點(diǎn)距離其實(shí)等于(dis(u,v)+dis(u,w)+dis(v,w))/2;
方法一:離線算法:tarjan和并查集
#include<cstdio>
#include <algorithm>
#include<cstring>
using namespace std;
const int MAXN=50010;
const int BLACK=1,WHITE=0;
int father[MAXN],vis[MAXN],dis[MAXN],color[MAXN];
int parent(int u)
{
while(u!=father[u])
{
father[u]=father[father[u]];
u=father[u];
}
return u;
}
void connect(int u,int v)//v作為u的兒子
{
int fu=parent(u);
int fv=parent(v);
father[fv]=fu;
}
struct Node
{
int to,next,val;
};
Node edge[MAXN<<1];
int head[MAXN],cnt;
void addEdge(int u,int v,int val)
{
edge[cnt].to=v;
edge[cnt].val=val;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].to=u;
edge[cnt].val=val;
edge[cnt].next=head[v];
head[v]=cnt++;
}
struct Ask
{
int u,to,next,lca;
};
Ask arr[70010*6];
int ask_head[MAXN],idx;
void addAsk(int u,int v)
{
arr[idx].u=u;arr[idx].to=v;arr[idx].next=ask_head[u];ask_head[u]=idx++;
arr[idx].u=v;arr[idx].to=u;arr[idx].next=ask_head[v];ask_head[v]=idx++;
}
void tarjan(int u)
{
vis[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]==0)
{
dis[v]=dis[u]+edge[i].val;
tarjan(v);
connect(u,v);
}
}
color[u]=BLACK;
for(int i=ask_head[u];i!=-1;i=arr[i].next)
{
int v=arr[i].to;
if(color[v]==BLACK)
{
int fa=parent(v);
arr[i].lca=fa;
arr[i^1].lca=fa;
}
}
}
int main()
{
int t,n,m,u,v,w,val,cas=0;
while(scanf("%d",&n)!=EOF)
{
if(cas++) printf("\n");
memset(head,-1,sizeof(head));
memset(ask_head,-1,sizeof(ask_head));
memset(vis,0,sizeof(vis));
memset(color,WHITE,sizeof(color));
for(int i=1;i<=n;i++) father[i]=i;
cnt=idx=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&val);
addEdge(u+1,v+1,val);
}
scanf("%d",&m);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&u,&v,&w);
addAsk(u+1,v+1);addAsk(u+1,w+1);addAsk(v+1,w+1);
}
dis[1]=0;
tarjan(1);
for(int i=0;i<idx;i+=6)
{
int d1=dis[arr[i].u]+dis[arr[i].to]-2*dis[arr[i].lca];
int d2=dis[arr[i+2].u]+dis[arr[i+2].to]-2*dis[arr[i+2].lca];
int d3=dis[arr[i+4].u]+dis[arr[i+4].to]-2*dis[arr[i+4].lca];
printf("%d\n",(d1+d2+d3)/2);
}
}
}
方法二:在線算法: RMQ算法
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=50010;
const int MAXE=MAXN*2;
struct Node
{
int to,next,val;
};
Node edge[MAXE];
int head[MAXN],cnt;
void addEdge(int u,int v,int val)
{
edge[cnt].to=v;
edge[cnt].val=val;
edge[cnt].next=head[u];
head[u]=cnt++;
}
int first[MAXN];
int vis[MAXN];
int deep[MAXN*2];
int sequence[MAXN*20];
int dis[MAXN];
int dp[MAXN*2][25];
int tot;
void DFS(int u,int dep)
{
vis[u]=1;
sequence[++tot]=u;deep[tot]=dep;first[u]=tot;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]==0)
{
dis[v]=dis[u]+edge[i].val;
DFS(v,dep+1);
sequence[++tot]=u;
deep[tot]=dep;
}
}
}
void ST(int len)
{
for(int i=1;i<=len;i++) dp[i][0]=i;
for(int j=1;(1<<j)<=len;j++)
{
for(int i=1;i+(1<<j)-1<=len;i++)
{
int a=dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
if(deep[a]<deep[b]) dp[i][j]=dp[i][j-1];
else dp[i][j]=dp[i+(1<<(j-1))][j-1];
}
}
}
int RMQ(int x,int y)
{
int k=0;
while((1<<(k+1))<=y-x+1) k++;
return deep[dp[x][k]]<deep[dp[y-(1<<k)+1][k]]?dp[x][k]:dp[y-(1<<k)+1][k];
}
int LCA(int u,int v)
{
int x=first[u],y=first[v];
if(x>y) swap(x,y);
return sequence[RMQ(x,y)];
}
int main()
{
int n,m,u,v,val,a,b,c;
bool start=true;
while(scanf("%d",&n)!=EOF)
{
if(!start) printf("\n");
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
cnt=tot=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&val);
addEdge(u+1,v+1,val);
addEdge(v+1,u+1,val);
}
dis[1]=0;
DFS(1,1);
ST(tot);
scanf("%d",&m);
while(m--)
{
scanf("%d%d%d",&a,&b,&c);
a++;b++;c++;
int d1=dis[a]+dis[b]-2*dis[LCA(a,b)];
int d2=dis[a]+dis[c]-2*dis[LCA(a,c)];
int d3=dis[b]+dis[c]-2*dis[LCA(b,c)];
printf("%d\n",(d1+d2+d3)/2);
}
start=false;
}
}
題目推薦:
CODEVS 2370 小機(jī)房的樹
CODEVS 1036 商務(wù)旅行
METO CODE 223 拉力賽
HDU 2586 How far way?
ZOJ 3195 Design the city