lca倍增法与欧拉序
记录做题记录方便复习//落谷3379
一、倍增法
倍增法关键在于两者先跳跃到同一深度之后同时向上跳,是暴力解法的优化
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) using namespace std; inline int read(){//快读 int x=0,s=1; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')s=-1;ch=getchar();} while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*s; }#define maxn 500005 int n,m,s; int dep[maxn],f[maxn][20],lg[maxn];//存储深度;i向上跳跃2的y次方到达的位置;log值 struct node{ int to,next; }b[maxn<<1]; int head[maxn],cnt; inline void add(int u,int v){//链式前向星存图 b[++cnt].to=v; b[cnt].next=head[u]; head[u]=cnt; } void dfs(int now,int fa){//当前节点;父亲节点 f[now][0]=fa;//往上跳2的0次方即为其父亲节点 dep[now]=dep[fa]+1;更新当前节点深度 for(int i=1;i<=lg[dep[now]];++i)//更新当前节点跳跃2的i次方到达的点 f[now][i]=f[f[now][i-1]][i-1];//当前节点向上跳跃2的i次方相当于向上跳跃2的2-1次方后再跳跃2的i-1次方 for(int i=head[now];i;i=b[i].next)//当前节点向下深搜 if(b[i].to!=fa) dfs(b[i].to,now); } int lca(int x,int y){ if(dep[x]<dep[y]) swap(x,y);//保证x深度大于y,便于操作 while(dep[x]>dep[y]) x=f[x][lg[dep[x]-dep[y]]];//先跳跃到同一深度 if(x==y) return x;//深度相同为同一节点即为lca for(int k=lg[dep[x]];k>=0;k--) if(f[x][k]!=f[y][k])//从大到小同时向上跳跃到第一个不相同的节点 x=f[x][k],y=f[y][k]; return f[x][0];//该节点的父亲节点即为lca } int main(){ n=read(),m=read(),s=read(); int x,y; rep(i,1,n-1){ x=read(),y=read(); add(x,y);add(y,x); } rep(i,2,n){ lg[i]=lg[i>>1]+1;//前期log处理 } dfs(s,0);//从根节点往下深搜 rep(i,1,n){ x=read(),y=read(); printf("%d\n",lca(x,y)); } return 0; }
二、欧拉序
基于欧拉序利用st表得到任意两节点的lca
欧拉序即为遍历树的过程记录当前节点出现的次数与当前的深度
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) using namespace std; inline int read(){ int x=0,s=1; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')s=-1;ch=getchar();} while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*s; } #define maxn 500005 int n,m,s,h[maxn],cnt; int lg[maxn<<1],first[maxn],f[maxn<<1][20],dep[maxn<<1],val[maxn<<1],tot;//log值;某个点第一次在欧拉序中出现的位置;st表;欧拉序得到的深度;欧拉序得到的值;树的当前位置; struct node{ int to,next; }b[maxn<<1]; void add(int u,int v){//链式前向星存图 b[++cnt].to=v; b[cnt].next=h[u]; h[u]=cnt; } void dfs(int u,int d,int fa){//当前节点;深度;父亲节点 dep[++tot]=d,first[u]=tot,val[tot]=u;//记录节点第一次出现时欧拉序及深度 for(int i=h[u];i;i=b[i].next)//从当前节点向下深搜 if(b[i].to!=fa){ dfs(b[i].to,d+1,u); dep[++tot]=d,val[tot]=u;//返回时记录欧拉序及深度 } } void st(){ rep(i,2,tot) lg[i]=lg[i>>1]+1;//前期log处理 rep(i,1,tot) f[i][0]=i;//当前节点向后2的0次方-1得到的最小深度节点是本身 rep(i,1,lg[tot]) rep(j,1,tot+1-(1<<i)){//st表处理方式将一个区间分为两个有覆盖的小区间 int ta=f[j][i-1]; int tb=f[j+(1<<(i-1))][i-1]; if(dep[ta]<=dep[tb]) f[j][i]=ta;//根据小区间最小深度更新大区间最小深度 else f[j][i]=tb; } } int main(){ n=read(),m=read(),s=read(); int x,y; rep(i,1,n-1){ x=read(),y=read(); add(x,y),add(y,x); } dfs(s,1,0);//根节点出发得到欧拉序 st(); rep(i,1,m){ x=read(),y=read(); x=first[x],y=first[y];//求lca必须为第一次写出现的位置 if(x>y) swap(x,y);//保证x为小值 int t=lg[y-x+1]; int ta=f[x][t]; int tb=f[y-(1<<t)+1][t]; if(dep[ta]<=dep[tb]) printf("%d\n",val[ta]); else printf("%d\n",val[tb]); } return 0; }

浙公网安备 33010602011771号