三种方法:
1.树链剖分(在上一篇代码中已经讲解得很详细,不再一一赘述)
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<climits> #include<ctime> #include<queue> #include<vector> #include<map> #include<algorithm> #include<iomanip> using namespace std; #define rep(i,a,b) for(int i=a;i<=b;i++) #define dep(i,a,b) for(int i=a;i>=b;i--) typedef long long LL; inline int read(){ int x=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x; } const int M=500001; int n,m,root,cnt=0,head[M],to[M<<1],next[M<<1],son[M],siz[M],top[M],dep[M],fa[M]; void Insert(int u,int v){ to[cnt]=v; next[cnt]=head[u]; head[u]=cnt++; to[cnt]=u; next[cnt]=head[v]; head[v]=cnt++; } void dfs1(int u){ siz[u]=1; for(int i=head[u];i!=-1;i=next[i]){ int v=to[i]; if(v!=fa[u]){ fa[v]=u; dep[v]=dep[u]+1; dfs1(v); siz[u]+=siz[v]; if(!son[u]||siz[son[u]]<siz[v])son[u]=v; } } } void dfs2(int u){ if(son[u]){ top[son[u]]=top[u]; dfs2(son[u]); } for(int i=head[u];i!=-1;i=next[i]){ int v=to[i]; if(v!=fa[u]&&v!=son[u]){ top[v]=v; dfs2(v); } } } int lca(int x,int y){ while(1){ int tx=top[x],ty=top[y]; if(tx==ty)return (dep[x]<dep[y]?x:y); if(dep[tx]<dep[ty])y=fa[ty];else x=fa[tx]; } } int main(){ n=read();m=read();root=read(); memset(head,-1,sizeof(head)); rep(i,1,n-1){ int x=read(),y=read(); Insert(x,y); } dep[root]=1; dfs1(root); top[root]=root; dfs2(root); while(m--){ int x=read(),y=read(); printf("%d\n",lca(x,y)); } return 0; }
2.Tarjan(慎用!如果题目是按照树剖卡常数的话,则此算法会MLE(空间大小为树剖的两倍))
有个特别形象的讲解在这里:http://www.cnblogs.com/JVxie/p/4854719.html
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<vector> #include<map> #include<algorithm> #include<climits> #include<ctime> #include<iomanip> using namespace std; #define rep(i,a,b) for(int i=a;i<=b;i++) #define dep(i,a,b) for(int i=a;i>=b;i--) inline int read(){ int x=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x; } const int M=500001; int n,m,root; bool vis[M]={0}; int cnt=0,tot=0,f[M],ans[M],to[M<<1],head[M],next[M],q[M<<1],nt[M<<1],h[M]; void insert_edge(int x,int y){ to[cnt]=y; next[cnt]=head[x]; head[x]=cnt++; to[cnt]=x; next[cnt]=head[y]; head[y]=cnt++; } void insert_query(int x,int y){ q[tot]=y; nt[tot]=h[x]; h[x]=tot++; q[tot]=x; nt[tot]=h[y]; h[y]=tot++; } int Find(int x){ if(f[x]==x)return x; else return f[x]=Find(f[x]); } void dfs(int fa,int u){ for(int i=head[u];i!=-1;i=next[i]){ int v=to[i]; if(v!=fa)dfs(u,v); } for(int i=h[u];i!=-1;i=nt[i]){ int v=q[i]; if(vis[v])ans[i/2+1]=Find(v); } vis[u]=1; f[u]=fa; } int main(){ n=read();m=read();root=read(); memset(head,-1,sizeof(head)); memset(h,-1,sizeof(h)); rep(i,1,n)f[i]=i; rep(i,1,n-1){ int x=read(),y=read(); insert_edge(x,y); } rep(i,1,m){ int x=read(),y=read(); insert_query(x,y); } dfs(root,root); rep(i,1,m)printf("%d\n",ans[i]); return 0; }
3.倍增
这个算法思想非常重要,首先保存每个节点向上走的第1、2、4、8...个祖先,复杂度是log级别的,然后找LCA的时候就可以快速查找了。
这个算法中用到了一些位运算的知识,需要用心体会。
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<vector> #include<map> #include<algorithm> #include<climits> #include<ctime> #include<iomanip> #include<cmath> using namespace std; #define rep(i,a,b) for(int i=a;i<=b;i++) #define dep(i,a,b) for(int i=a;i>=b;i--) inline int read(){ int x=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x; } const int M=500001; int dp[M][19],to[M<<1],next[M<<1],head[M],dep[M]; int cnt=0,n,m,root; void insert_edge(int x,int y){ to[cnt]=y; next[cnt]=head[x]; head[x]=cnt++; to[cnt]=x; next[cnt]=head[y]; head[y]=cnt++; } void dfs(int u){ for(int i=head[u];i!=-1;i=next[i]){ int v=to[i]; if(v!=dp[u][0]){ dep[v]=dep[u]+1;//记录深度 dp[v][0]=u;//保存某个节点的父亲节点,即第1个祖先 dfs(v); } } } void init(){ for(int j=1;(1<<j)<=n;j++)//预处理,超出根节点的部分不用管,计算LCA的时候不会超出根节点 rep(i,1,n)dp[i][j]=dp[dp[i][j-1]][j-1];//一个节点的第2^i个祖先即为它的第2^(i-1)个祖先的第2^(i-1)个祖先 } int lca(int x,int y){ if(dep[x]>dep[y])swap(x,y);//保证y的深度较大 int f=dep[y]-dep[x];//计算深度差 for(int i=0;(1<<i)<=f;i++)//首先将x与y移动到同一深度上 if((1<<i)&f)y=dp[y][i];//尤其注意这里的位运算,我们需要将y向上移动f位,首先将f表示成二进制,(1<<i)&f的值为1表示f从右往左第i位为1,则将y向上移动2^i位 if(x==y)return x;//若x和y是同一节点,则lca已求出 else{ dep(i,(int)log2(n),0)if(dp[x][i]!=dp[y][i])x=dp[x][i],y=dp[y][i];//从最大跨度开始逆推,若dp[x][i]!=dp[y][i],说明将x和y同时向上移动2^i步后仍没有找到lca,则修改x和y,若已经找到了说明lca为该位置的某个后代节点,则不修改 return dp[x][0];//注意循环的最后一次操作,如果dp[x][0]和dp[y][0]相同,没有进行修改,则x和y的父亲节点即为所求的lca } } int main(){ n=read();m=read(),root=read(); memset(head,-1,sizeof(head)); dp[root][0]=root; rep(i,1,n-1){ int x=read(),y=read(); insert_edge(x,y); } dep[root]=1; dfs(root);//一次dfs初始化父亲节点 init(); while(m--){ int x=read(),y=read(); printf("%d\n",lca(x,y)); } return 0; }