LCA:最近公共祖先html
指在有根樹中,找出某兩個結點u和v最近的公共祖先node
如圖,5,7的最近公共祖先就是3ios
接下來,咱們來了解如何求解LCAspa
No.1 暴力code
首先想到的確定是暴力,咱們搜索,從兩個節點一步一步向上爬。htm
待你爬到之時,你天然會感到TLE的魅力blog
複雜度:O(nm)(最壞)ci
No.2 倍增法get
倍增的主要思想就是,讓較深的節點向上爬,爬到和較淺的節點同高度(而後它們就相愛了,不過它優的是,它不是一個一個向上爬string
即:先搜一遍樹,預處理出x的第(1<=2k<=max(dep))個父親,存起來.詢問時,仍是讓深度更大的節點x向上倍增至與另外一節點y在同一深度上,而後一塊兒倍增向上跳
建樹:
求解:
由於涉及倍增,因此倍增法在複雜度方面確定比暴力更優,降n爲logn
複雜度:(mlogn)
No.3 離線tarjan法
咱們先將全部詢問存起來,DFS一遍的同時咱們將已經回溯完的標記爲'′1′′,正在dfs的及dfs過但未回溯的標記爲''2'
而後在正在dfs的節點中處理與它有關的詢問,若正在回溯已經DFS過的節點x,有個詢問是求LCA(x,y)。
若y的標記是′′1′′,顯然y第一個標記爲′′2′′的祖先就爲LCA(x,y)。
若標記不是''1'',好比當y是x祖宗仍是不要緊,在回溯到y時,y就是符合要求的答案。
那怎麼快速求第一個標記爲'′′2′′的祖先呢?用並查集維護一下就行了.
顯然,此種方法的複雜度更優。可是,咱們容易發現,它只能離線求解,因此它適於數據範圍較大且離線操做的題目。
複雜度O(N+M)
No.4 ST法
由歐拉序,通過ST的預處理後,比較大小,小的看成左區間l,大的看成右區間r,以後查詢l<=i<=r裏面使得deep[i]最小的值,返回對應下標即最近公共祖先。
複雜度:O(n+m+nlogn)
最後的重頭戲,恩,,我最喜歡的一種方法(學姐教的
樹剖大法:
首先,咱們要建樹!
在建樹的過程當中,咱們能夠求出每一個點的深度,每一個點的父節點,以及每棵子樹的大小。
接下來咱們要處理輕鏈和重鏈
處理好輕鏈重鏈以後,咱們能夠根據判斷它們在那條鏈上,以便於求解。
複雜度:O(mlogn)
以上乾貨報道完畢。
如下是代碼time:
1.暴力就不發了。
2.倍增:
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int maxn=500000+2; int n,m,s,k; int head[maxn],deep[maxn],dad[maxn][21]; struct node { int v,next; } e[maxn*2]; void add(int u,int v) { e[k].v=v; e[k].next=head[u]; head[u]=k++; e[k].v=u; e[k].next=head[v]; head[v]=k++; } void dfs(int u,int fa) { deep[u]=deep[fa]+1; dad[u][0]=fa; for(int i=1; (1<<i)<=deep[u]; i++) dad[u][i]=dad[dad[u][i-1]][i-1]; for(int i=head[u]; i!=-1; i=e[i].next) { int v=e[i].v; if(v!=fa) dfs(v,u); } } int lca(int a,int b) { if(deep[a]>deep[b]) swap(a,b); for(int i=20; i>=0; i--) if(deep[a]<=deep[b]-(1<<i)) b=dad[b][i]; if(a==b) return a; for(int i=20; i>=0; i--) { if(dad[a][i]==dad[b][i]) continue; else a=dad[a][i],b=dad[b][i]; } return dad[a][0]; } int main() { memset(head,-1,sizeof(head)); int a,b; scanf("%d%d%d",&n,&m,&s); for(int i=1; i<n; i++) { scanf("%d%d",&a,&b); add(a,b); } dfs(s,0); for(int i=1; i<=m; i++) { scanf("%d%d",&a,&b); printf("%d\n",lca(a,b)); } return 0; }
3.離線tarjan
#include <map> #include <queue> #include <cstdio> #include <cctype> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #define ll long long #define ri register int #define ull unsigned long long using namespace std; const int maxn=500005; const int inf=0x7fffffff; int n,m,s,t; struct Edge{ int ne,to; }edge[maxn<<1]; struct node{ int d,id; node(int x,int y){d=x,id=y;} node(){;} }; vector <node>q[maxn]; int h[maxn],num_edge,ans[maxn]; inline void add_edge(int u,int v){ edge[++num_edge].ne=h[u]; edge[num_edge].to=v; h[u]=num_edge; edge[++num_edge].ne=h[v]; edge[num_edge].to=v; h[v]=num_edge; } int fa[maxn],vis[maxn]; int get(int x){ if(fa[x]!=x)fa[x]=get(fa[x]); return fa[x]; } void dfs(int now){ int u,v; vis[now]=1; for(ri i=h[now];i;i=edge[i].ne){ v=edge[i].to; if(vis[v])continue; dfs(v); fa[v]=now; } for(ri i=0;i<q[now].size();i++){ u=q[now][i].d,v=q[now][i].id; if(vis[u]==2) ans[v]=get(u); } vis[now]=2; return ; } int main(){ int x,y; cin>>n>>m>>s; for(ri i=1;i<n;i++){ cin>>x>>y; add_edge(x,y); fa[i]=i; } fa[n]=n; for(ri i=1;i<=m;i++){ cin>>x>>y; q[x].push_back(node(y,i)); q[y].push_back(node(x,i)); } dfs(s); for(ri i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
4.ST表
#include<cstdio> #include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<queue> using namespace std; int n,m,s,x,y,tot,cnt; const int N=500005,M=1000005; int head[N],to[M],nxt[M],deep[M],vis[M],size[M]; int dad[M][20],dis[M]; void add(int x,int y) { to[++tot]=y; nxt[tot]=head[x]; head[x]=tot; to[++tot]=x; nxt[tot]=head[y]; head[x]=tot; } void DFS(int u,int fa,int l) { size[u]=++cnt; vis[cnt]=u; deep[cnt]=l; for(int i=head[u]; i; i=nxt[i]) { int v=to[i]; if(v==fa) continue; DFS(v,u,l+1); vis[++cnt]=u; deep[cnt]=l; } return ; } void RMQ() { for(int i=1; i<=cnt; i++) dis[i]=dis[i-1]+(1<<dis[i-1]==i); for(int i=1; i<=cnt; i++) dad[i][0]=i; for(int i=1; (1<<i)<=cnt; i++) { for(int j=1; j+(1<<i)-1<=cnt; j++) { int a=dad[j][i-1]; int b=dad[j+(1<<(i-1))][i-1]; if(deep[a]<=deep[b]) dad[j][i]=a; else dad[j][i]=b; } } return ; } int ST(int x,int y) { int r=size[x],l=size[y]; if(r<l) swap(r,l); int k=dis[r-l+1]-1,a=dad[l][k],b=dad[r-(1<<k)+1][k]; if(deep[a]<=deep[b]) return vis[a]; else return vis[b]; } int main() { cin>>n>>m>>s; for(int i=1; i<n; i++) { cin>>x>>y; add(x,y); } DFS(s,0,1); RMQ(); for(int i=1; i<=m; ++i) { cin>>x>>y; cout<<ST(x,y); } return 0; }
5.樹剖
/* 註釋掉的代碼是當有邊權時的寫法 */ #include <algorithm> #include <cstring> #include <cstdio> using namespace std; const int M = 500005; int n, m, tot; int to[M*2], net[M*2], head[M];// cap[M*2]; int deep[M], top[M], size[M], dad[M];//length[M]; void add(int u, int v, int w) { to[++tot] = v; net[tot] = head[u]; head[u] = tot;// cap[tot] = w; to[++tot] = u; net[tot] = head[v]; head[v] = tot;// cap[tot] = w; } void dfs(int now) { //建樹 size[now] = 1; deep[now] = deep[dad[now]] + 1; for (int i = head[now]; i; i = net[i]) if (to[i] != dad[now]) { dad[to[i]] = now; // length[to[i]] = length[now] + cap[i]; dfs(to[i]); size[now] += size[to[i]]; } } void dfsl(int now) { //處理輕重鏈 int t = 0; if (!top[now]) top[now] = now; for (int i = head[now]; i; i = net[i]) //求重兒子 if (to[i] != dad[now] && size[to[i]] > size[t]) t = to[i]; if (t) { //處理重鏈 top[t] = top[now]; dfsl(t); } for (int i = head[now]; i; i = net[i]) //處理輕鏈 if (to[i] != dad[now] && to[i] != t) dfsl(to[i]); } int lca(int x, int y) { //求LCA while (top[x] != top[y]) { if (deep[top[x]] < deep[top[y]]) swap(x, y); x = dad[top[x]]; } return deep[x] > deep[y] ? y : x; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i < n; ++i) { //由於樹的邊有n-1條,因此循環要i<n int u, v; scanf("%d%d", &u, &v); add(u, v); /* int u, v, w; scanf("%d%d%d", &u, &v, &w); add(u, v, w); */ } dfs(1); //通常題目默認根節點爲1號節點,若是不是,將這兩個dfs括號中的1換爲題目中規定的節點便可 dfsl(1); for (int i = 1; i <= m; ++i) { //查詢LCA的次數 int u, v; scanf("%d%d", &u, &v); printf("%d\n", lca(u, v)); } return 0; }
一世安寧