题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入输出格式
输入格式:第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
样例说明:
该树结构如下:
第一次询问:2、4的最近公共祖先,故为4。
第二次询问:3、2的最近公共祖先,故为4。
第三次询问:3、5的最近公共祖先,故为1。
第四次询问:1、2的最近公共祖先,故为4。
第五次询问:4、5的最近公共祖先,故为4。
故输出依次为4、4、1、4、4。
题解
其实是放一下代码
众所周知,LCA有几种常见的做法
- 暴力跳
- 先把较深的往上跳,跳到同一深度
- 然后一起跳
- 单次复杂度$O(n)$分分钟带你上天
- 倍增
- 在跳的时候优化一下,不一格一格的跳,而是拆分成二进制跳
- 是对暴力跳选手思维难度上最友好的升级方式
- 需要预处理出每个点往上$2^i$步的祖先,
- 时间复杂度$O(nlogn+mlogn)$,空间复杂度$O(nlogn)$
-
1 /* 2 qwerta 3 P3379 【模板】最近公共祖先(LCA) 4 Accepted 5 100 6 代码 C++,1.37KB 7 提交时间 2018-03-13 18:33:35 8 耗时/内存 9 1672ms, 51789KB 10 */ 11 #include<iostream> 12 #include<cstdio> 13 #include<algorithm> 14 using namespace std; 15 struct emm{ 16 int f,e; 17 }a[1000007]; 18 int h[500007]; 19 int d[500007]; 20 int p[500007][20]; 21 void dfs(int no,int fa) 22 { 23 d[no]=d[fa]+1; 24 //cout<<no<<" "<<d[no]<<" "<<fa<<endl; 25 p[no][0]=fa; 26 int w; 27 for(w=1;w<20;w++) 28 p[no][w]=p[p[no][w-1]][w-1]; 29 for(w=h[no];w;w=a[w].f) 30 { 31 if(a[w].e!=fa) 32 dfs(a[w].e,no); 33 } 34 return; 35 } 36 int main() 37 { 38 int c=0,x,y,n,m,s,i,j; 39 scanf("%d%d%d",&n,&m,&s); 40 for(i=1;i<n;++i) 41 { 42 scanf("%d%d",&x,&y); 43 ++c; 44 a[c].f=h[x]; 45 h[x]=c; 46 a[c].e=y; 47 ++c; 48 a[c].f=h[y]; 49 h[y]=c; 50 a[c].e=x; 51 d[i]=99999999; 52 } 53 d[n]=99999999; 54 dfs(s,0); 55 for(i=1;i<=m;++i) 56 { 57 scanf("%d%d",&x,&y); 58 if(d[x]<d[y])swap(x,y); 59 for(j=19;j>=0;--j) 60 { 61 if((d[x]-d[y])>=(1<<j)) 62 { 63 x=p[x][j]; 64 //cout<<x<<" "; 65 } 66 } 67 if(x==y)printf("%d ",x); 68 else{ 69 for(j=19;j>=0;--j) 70 { 71 if(p[x][j]!=p[y][j]) 72 { 73 x=p[x][j]; 74 y=p[y][j]; 75 } 76 } 77 printf("%d ",p[x][0]);} 78 } 79 /* 80 for(i=1;i<=n;i++) 81 { 82 cout<<i<<" "; 83 for(j=0;j<=19;j++) 84 cout<<p[i][j]<<" "; 85 cout<<endl; 86 }*/ 87 return 0; 88 } 89 90 倍增求LCA
- ST表
- 原理:dfs序在这两点之间 的点中,深度最小的点为lca
- 所以记录dfs序和深度,在区间上找最小值,转化为RMQ问题。
- 需要预处理dfs序和ST表。
- 时间复杂度$O(n+nlogn+mlogn)$,空间复杂度$O(3*n+nlogn)$
- 理论上比倍增慢一丢丢。
-
1 /* 2 qwerta 3 P3379 【模板】最近公共祖先(LCA) 4 Accepted 5 100 6 代码 C++,2.28KB 7 提交时间 2018-06-24 11:36:03 8 耗时/内存 9 1992ms, 98929KB 10 */ 11 #include<cstdio> 12 using namespace std; 13 int n,m,s; 14 struct emm{ 15 int e,f; 16 }a[1000007]; 17 int h[500007]; 18 int c; 19 inline int read() 20 { 21 int x=0; 22 char c=getchar(); 23 while(c<'0'||c>'9') c=getchar(); 24 while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); 25 return x; 26 } 27 inline void write(int x) 28 { 29 if(x>9) write(x/10); 30 putchar(x%10+'0'); 31 return; 32 } 33 inline int min(int qwq,int qaq){return qwq<qaq?qwq:qaq;} 34 inline void swap(int &qq,int &ww){int ee=qq;qq=ww;ww=ee;return;} 35 inline void con(int q,int w) 36 { 37 a[++c].f=h[q]; 38 h[q]=c; 39 a[c].e=w; 40 return; 41 } 42 inline void scan() 43 { 44 n=read(),m=read(),s=read(); 45 //scanf("%d%d%d",&n,&m,&s); 46 int x,y; 47 for(register int i=1;i<n;++i) 48 { 49 x=read(),y=read(); 50 //scanf("%d%d",&x,&y); 51 con(x,y); 52 con(y,x); 53 } 54 return; 55 } 56 int fir[500007]; 57 int pl[1000007]; 58 int d[1000007]; 59 int f[1000007][21]; 60 int dd; 61 void dfs(int x) 62 { 63 d[x]=++dd; 64 pl[++c]=x; 65 //printf("%d %d %d %d ",c,x,d[c],pl[c]); 66 if(!fir[x])fir[x]=c; 67 for(register int i=h[x];i;i=a[i].f) 68 { 69 int u=a[i].e; 70 if(!fir[u]) 71 { 72 dfs(u); 73 pl[++c]=x; 74 //printf("%d %d %d %d ",c,x,d[c],pl[c]); 75 } 76 } 77 --dd; 78 return; 79 } 80 inline void rmq() 81 { 82 for(register int i=1;i<=c;++i) 83 f[i][0]=pl[i]; 84 for(register int j=1;(1<<j)<=c;++j) 85 for(register int i=1;i+(1<<j)-1<=c;++i) 86 { 87 if(d[f[i][j-1]]<d[f[i+(1<<(j-1))][j-1]]) 88 f[i][j]=f[i][j-1]; 89 else f[i][j]=f[i+(1<<(j-1))][j-1]; 90 } 91 return; 92 } 93 inline void predo() 94 { 95 c=0; 96 dfs(s); 97 rmq(); 98 return; 99 } 100 inline void find(int x,int y) 101 { 102 int l=fir[x],r=fir[y]; 103 if(l>r)swap(l,r); 104 int p; 105 //cout<<l<<" "<<r<<" "; 106 for(register int j=20;j>=0;--j) 107 if(l+(1<<j)-1<=r) 108 { 109 //cout<<f[l][j]<<" "<<f[r-(1<<j)+1][j]<<endl; 110 //cout<<l<<" "<<r<<" "<<j<<endl; 111 p=d[f[l][j]]<d[f[r-(1<<j)+1][j]] 112 ?f[l][j]:f[r-(1<<j)+1][j]; 113 //p=min(f[l][j],f[r-(1<<j)+1][j]); 114 write(p);putchar(' '); 115 return; 116 } 117 //cout<<"a"<<endl; 118 return; 119 } 120 inline void run() 121 { 122 for(register int i=1;i<=m;++i) 123 { 124 int x,y; 125 scanf("%d%d",&x,&y); 126 find(x,y); 127 } 128 return; 129 } 130 int main() 131 { 132 scan(); 133 predo(); 134 run(); 135 return 0; 136 }
- tarjan
- 和求强连通分量的tarjan不是一个东西。
- 不太了解,应该是最小众的做法了叭,据说有常数上的优势?
- 其实以前是听懂过的,但是仗着自己已经会两种做法了就飘了没写
- 树链剖分
- 乍一听挺二了吧唧的,以前觉得像各种A+B的题解一样装哔
- 但是自从会了树剖之后我的倍增和ST表就忘光了a!(不知道该开心还是难过
- 对于会树剖的选手而言,又好写又不用过脑子还快。
- 需要预处理遍历两遍,和做一个gettop的操作,正式跑的过程应该比倍增和ST快。
- 反正我写出来快了不少。
-
1 /* 2 qwerta 3 P3379 【模板】最近公共祖先(LCA) 4 Accepted 5 100 6 代码 C++,1.48KB 7 提交时间 2018-10-09 19:16:23 8 耗时/内存 9 1043ms, 20392KB 10 */ 11 #include<cstdio> 12 #include<iostream> 13 using namespace std; 14 #define R register 15 const int MAXN=500007; 16 struct emm{ 17 int e,f; 18 }a[2*MAXN]; 19 int h[MAXN]; 20 int tot=0; 21 void con(int x,int y) 22 { 23 a[++tot].f=h[x]; 24 h[x]=tot; 25 a[tot].e=y; 26 a[++tot].f=h[y]; 27 h[y]=tot; 28 a[tot].e=x; 29 return; 30 } 31 int fa[MAXN],d[MAXN],siz[MAXN],z[MAXN],top[MAXN]; 32 void dfs(int x) 33 { 34 siz[x]=1; 35 top[x]=x; 36 int mac=0,macc=-1; 37 for(R int i=h[x];i;i=a[i].f) 38 if(!d[a[i].e]) 39 { 40 d[a[i].e]=d[x]+1; 41 fa[a[i].e]=x; 42 dfs(a[i].e); 43 siz[x]+=siz[a[i].e]; 44 if(siz[a[i].e]>macc){mac=a[i].e;macc=siz[a[i].e];} 45 } 46 z[x]=mac; 47 top[mac]=x; 48 return; 49 } 50 int fitop(int x) 51 { 52 if(top[x]==x)return x; 53 return top[x]=fitop(top[x]); 54 } 55 inline int read() 56 { 57 char ch=getchar(); 58 int x=0; 59 while(!isdigit(ch))ch=getchar(); 60 while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} 61 return x; 62 } 63 void write(int x) 64 { 65 if(x>9)write(x/10); 66 putchar(x%10+'0'); 67 return; 68 } 69 int main() 70 { 71 //freopen("a.in","r",stdin); 72 int n=read(),m=read(),s=read(); 73 for(R int i=1;i<n;++i) 74 { 75 int x=read(),y=read(); 76 con(x,y); 77 } 78 d[s]=1; 79 dfs(s); 80 for(R int i=1;i<=n;++i) 81 top[i]=fitop(i); 82 for(R int c=1;c<=m;++c) 83 { 84 int u=read(),v=read(); 85 while(top[u]!=top[v]) 86 { 87 if(d[top[u]]>d[top[v]])u=fa[top[u]]; 88 else v=fa[top[v]]; 89 } 90 write(d[u]<d[v]?u:v); 91 putchar(' '); 92 } 93 return 0; 94 }
也许还有别的做法叭,太弱了不了解。
吸氧?卡常?不存在的我跟你说,
吸氧是不可能的,这辈子都不可能的,老子复杂度这么优秀吸什么氧?!
——我屮艸芔茻加个register吸个氧就减到三分之二?!这么香?!!
总结
在倍增和ST之间推荐倍增,思维难度低,效率还蛮不错。
但是也见过考试考RMQ问题的...我校倍增选手当场哭出声2333
然后会了树剖还写这些个毛,多难想啊2333
其实我只是放一下代码的qwq
UPD
我校选手看过来!
这里是我下午当场出锅的代码(qaq
1 /* 2 qwerta 3 P3379 【模板】最近公共祖先(LCA) 4 Accepted 5 100 6 代码 C++,1.18KB 7 提交时间 2018-10-14 16:54:45 8 耗时/内存 9 2037ms, 53444KB 10 */ 11 #include<cstdio> 12 #include<iostream> 13 #include<algorithm> 14 using namespace std; 15 #define R register 16 struct emm{ 17 int to,nxt; 18 }a[1000003]; 19 int h[500003];//邻接链表存图 20 int cnt=0; 21 inline void con(int x,int y)//连边 22 { 23 a[++cnt].nxt=h[x]; 24 h[x]=cnt; 25 a[cnt].to=y; 26 a[++cnt].nxt=h[y]; 27 h[y]=cnt; 28 a[cnt].to=x; 29 return; 30 } 31 int fa[500003],d[500003]; 32 void dfs(int x)//dfs建树 33 { 34 for(R int i=h[x];i;i=a[i].nxt) 35 if(!d[a[i].to]) 36 { 37 d[a[i].to]=d[x]+1; 38 fa[a[i].to]=x; 39 dfs(a[i].to); 40 } 41 return; 42 } 43 int la[500003][20];//向上2^j步的祖先 44 int main() 45 { 46 int n,m,s; 47 scanf("%d%d%d",&n,&m,&s); 48 for(R int i=1;i<n;++i) 49 { 50 int x,y; 51 scanf("%d%d",&x,&y);//读边 52 con(x,y); 53 } 54 d[s]=1; 55 fa[s]=s; 56 dfs(s); 57 for(R int i=1;i<=n;++i) 58 la[i][0]=fa[i]; 59 for(R int j=1;j<=19;++j) 60 for(R int i=1;i<=n;++i) 61 la[i][j]=la[la[i][j-1]][j-1]; 62 //cout<<endl; 63 for(R int i=1;i<=m;++i) 64 { 65 int x,y; 66 scanf("%d%d",&x,&y); 67 if(d[x]<d[y])swap(x,y); 68 // 69 for(R int j=19;j>=0;--j) 70 if(d[x]-(1<<j)>=d[y]) 71 x=la[x][j]; 72 //same depth 73 if(x==y){printf("%d ",y);continue;} 74 for(R int j=19;j>=0;--j) 75 if(la[x][j]!=la[y][j]) 76 x=la[x][j],y=la[y][j]; 77 //cout<<x<<" "<<y<<" "<<endl; 78 printf("%d ",fa[x]); 79 } 80 return 0; 81 }
都追到这里了要给老学姐点个关注吖!QAQ