先贴上学习的文章:
在用Tarjan算法求LCA时会出现超时的现象,因为是一层一层的往上跳,于是就有了用倍增法求LCA,是层地往上跳。复杂度:O(nlogn)
1.倍增法思想:
按2的倍数跳,不过是从大到小跳,32,16,8,4,2,1。原因是从小到大跳会出现“悔棋”的现象:
从小到大:5!=1+2+4
从大到小:5=4+1
5<32 (不跳)
5<16(不跳)
5<8(不跳)
5>=4(向上跳4格到1)
1<2(不跳)
1>=1(向上跳1格到0)
2.算法解析:
step1:预处理
目标:
- 在dfs的时候求出每个节点的深度。
- 求出fa[x][i]:=x节点向上跳
层后在哪个节点上,这里要注意,节点可能向上跳地太猛了,以至于都跳出了根节点,这种对我们做题没有意义,所以只要求出向上跳的最大层数(也就是节点深度dep[x])就可以啦~
可知:fa[x][0]=x的父节点
fa[x][1]=fa[x的父节点][0]=fa[fa[x][0]][0]
fa[x][i]=fa[fa[x][i-1]][i-1]
原因是:比如向上跳=8层:
当前i节点向上跳8/2=4=层,到达点j
再由j节点向上跳8/2=4=层,到达目的节点。(一共跳了
层)
void dfs(int now,int pre){
dep[now]=dep[pre]+1;
vis[now]=1;
fa[now][0]=pre;
for(int i=1;(1<<i)<=dep[now];i++){//向上跳的层数(1<<i)<=节点的深度dep[now]
fa[now][i]=fa[fa[now][i-1]][i-1];
}
for(int i=head[now];i!=-1;i=edge[i].nex){
int v=edge[i].to;
if(v==pre)continue;
dis[v]=dis[now]+edge[i].cost;
dfs(v,now);
}
}
step2:常数优化
节点n向上跳的范围是[1,log2(n)]
for(int i=1;i<=n;i++){//求出log2(i)+1的值
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
可以模拟一下:
lg[1]=0+(1==1)=1
lg[2]=1+(1==1)=2
lg[3]=2+(4==3)=2
lg[4]=2+(4==4)=3
这里为什么用递推公式求log2(i)+1,而不用cmath里的函数求log2(i),原因是:
log是以e为底数, log10是以10为底数
求log2(i),则:log(i)/log(2)速度慢,不如求出log2(i)+1的值存在lg[i]数组里,需要的时候用lg[i]-1
step3:倍增LCA
有了上两步的铺垫,现在就好求多了。
求倍增LCA的思想是:
- 先把两个点提到统一高度,再一起跳。
- 向上跳也是有原则的:要跳到LCA的下面一层。然后该层对应节点的父节点就是要求的LCA
比如节点7和9,9先跳到8,此时和7是同一层,若是跳地太多到1,就会错认为1是LCA
正确的是7->4 9->8->5
fa[4][0]或fa[5][0]=3就是答案
int lca(int x,int y){
if(dep[x]<dep[y]){
swap(x,y);
}
while(dep[x]>dep[y]){
x=fa[x][lg[dep[x]-dep[y]]-1];
}
if(x==y)return x;
for(int k=lg[dep[x]];k>=0;k--){
if(fa[x][k]!=fa[y][k]){
x=fa[x][k];
y=fa[y][k];
}
}
return fa[x][0];
}
完结撒花✿✿ヽ(°▽°)ノ✿
上一道模板题:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<cmath>
#include<set>
#include<map>
using namespace std;
#define ll long long
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
typedef pair<int,int>P;
const int INF=0x3f3f3f3f;
const int N=100015;
int head[N],cnt,dis[N],pre[N];
int dep[N],fa[N][22],lg[N];
int vis[N];
struct A{
int to,nex,cost;
}edge[N];
int f(int x){
if(pre[x]==x)return pre[x];
pre[x]=f(pre[x]);
return pre[x];
}
void link(int a,int b){
int r1=f(a),r2=f(b);
if(r1!=r2){
pre[r2]=r1;
}
}
void add(int from,int to,int cost){
edge[cnt].to=to;
edge[cnt].cost=cost;
edge[cnt].nex=head[from];
head[from]=cnt++;
}
void dfs(int now,int pre){
dep[now]=dep[pre]+1;
vis[now]=1;
fa[now][0]=pre;
for(int i=1;(1<<i)<=dep[now];i++){
fa[now][i]=fa[fa[now][i-1]][i-1];
}
for(int i=head[now];i!=-1;i=edge[i].nex){
int v=edge[i].to;
if(v==pre)continue;
dis[v]=dis[now]+edge[i].cost;
dfs(v,now);
}
}
int lca(int x,int y){
if(dep[x]<dep[y]){
swap(x,y);
}
while(dep[x]>dep[y]){
x=fa[x][lg[dep[x]-dep[y]]-1];
}
if(x==y)return x;
for(int k=lg[dep[x]];k>=0;k--){
if(fa[x][k]!=fa[y][k]){
x=fa[x][k];
y=fa[y][k];
}
}
return fa[x][0];
}
void init(int n){
for(int i=1;i<=n;i++)pre[i]=i;
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(dep,0,sizeof(dep));
memset(dis,0,sizeof(dis));
cnt=0;
memset(fa,0,sizeof(fa));
}
int main(){
int n,m,k,s,t;
for(int i=1;i<=1000;i++){
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
while(scanf("%d%d%d",&n,&m,&k)!=EOF){
init(n);
int a,b,c;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
link(a,b);
add(a,b,c);
add(b,a,c);
}
for(int i=1;i<=n;i++){
if(!vis[i]){
dfs(i,0);
}
}
for(int i=1;i<=k;i++){
scanf("%d%d",&a,&b);
if(f(a)!=f(b)){
printf("Not connected\n");
}
else printf("%d\n",dis[a]+dis[b]-2*dis[lca(a,b)]);
}
}
}