求LCA可以用tarjan算法(见代码); 这里不详细介绍了。用到的东西主要是dfs + 并查集。 详见最下面的代码。
还有一种方法是利用 倍增的思想 求。
具体就是:
f[i][j] 表示i的第2^j次方个祖先是谁。 则f[i][j]= f[ f[i][j-1] ][j-1]; (j>0) f[i][j]=father[i];(j==0)
这样可以处理出每个节点的2^j次方的祖先是谁。然后对于每个询问p和q的LCA是谁。我们先考虑一种简单的情况:p和q在树的同一层(设为h)。让j>=log(h); 然后比较f[p][j]与f[q][j]是否相等,如果不等,则LCA(p,q)肯定在更高的层上。令p=f[p][j],q=f[q][j]; j--; 如果相等,j--; 这样用类似于二分的方法,可以求出LCA;
如果p和q不在同一层,假设h(p) > h(q); 可以先求出与q在同一层的p的祖先,然后用上面的办法求LCA。 怎么求与q在同一层的p的祖先呢? 可以用二分的方法来求。
for (log = 1; 1 << log <= h[p]; log++); log--; for (i = log; i >= 0; i--) if(h[p] - (1 << i) >= h[q]) p =f[p][i];
求RMQ有ST算法,思想与上面的差不多。 r[i][j]表示从第i位数字开始到第i+2^j-1位 中值最小的数字。 则 r[i][j] = min ( r[i][j-1] ,r[i+2^(j-1)][j-1] ). 初始化为 r[i][0] = a[i];
然后对于每组询问p和q。k=log(q-p+1) ; rmq( p, q) = min { r( p,k) ,r( q-2^k+1, k ) }
#include<stdio.h> // poj 3264 #include<math.h> #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)<(y)?(x):(y)) int r[50010][50],s[50010][50]; int a[50010]; int n; void initial() { int i,j,k; for(i=1;i<=n;i++) r[i][0]=s[i][0]=a[i]; for(j=1;1<<j<=n;j++) for(i=1;i+(1<<j)-1<=n;i++) { r[i][j]=min( r[i][j-1],r[i+(1<<(j-1))][j-1] ); s[i][j]=max( s[i][j-1],s[i+(1<<(j-1))][j-1] ); } } int main() { int q,i,j,k; int u,v; int maxh,minh; scanf("%d%d",&n,&q); for(i=1;i<=n;i++) scanf("%d",a+i); initial(); for(i=1;i<=q;i++) { scanf("%d%d",&u,&v); if(u==v) { printf("0\n"); continue; } j=(v-u+1); k=int(log10((double)j)/log10(2.0)); minh=min( r[u][k],r[v-(1<<k)+1][k] ); maxh=max( s[u][k],s[v-(1<<k)+1][k] ); printf("%d\n",maxh-minh); } return 0; }
至此,已经分别说了LCA和RMQ的算法。其实LCA和RMQ问题是等价的,即RMQ问题和LCA问题可以相互转换。下面就来简单说一说,怎么去转换两个问题;
一, LCA --> RMQ
可以通过dfs将树形结构转换为线形结构,得到欧拉序列。记录每个节点第一次出现的位置。 可以知道两个节点的LCA必定在这两个节点出现位置的中间,而且是深度最低的节点。我们在dfs的过程中可以记录每个点的深度。因此两个节点的LCA就转化为求对应的深度序列中两个位置间的最小值了。这就是RMQ了。由于深度序列每相邻两个元素间相差为1,所以叫做正负1RMQ。( 正负1RMQ是可以在O(N)的时间复杂度内求解的,主要就是把序列分快了,具体的我还没认真看)
二,RMQ---> LCA
构造笛卡尔树。具体构造过程忽略(可以上网搜),然后就转化为了LCA。( 然后再用一的方法再转化为RMQ时就转化为正负1RMQ了 ,就可以在O(N)时间内求解了 )
下面借用了别人的总结展示一下各个算法的时间复杂度:
所以说,LCA和RMQ问题是等价的,都可以做到在O(N)的时间内求解。如果题目时间要求没那么高,就不用这样转来转去的了。
下面是tarjan算法求LCA。(poj 1330)
#include<stdio.h> //poj 1330 #include<vector> using namespace std; intf[10010],r[10010]; vector<int>tree[10010]; int n; intindegree[10010]; intvis[10010],ans[10010]; int s,t; //存储询问,该题只有一组询问 void initial() { int i; for(i=1;i<=n;i++) { tree[i].clear(); f[i]=i; ans[i]=i; vis[i]=0; r[i]=1; indegree[i]=0; } } int find(int x) { if( f[x]==x ) return f[x]; return f[x]=find(f[x]); } int Unionset(intx,int y) // 按秩合并 { int fx,fy; fx=find(x); fy=find(y); if(fx==fy) return 0; if( r[fx] <= r[fy] ) { f[fx]=fy; r[fy]+=r[fx]; } else { f[fy]=fx; r[fx]+=r[fy]; } return 1; } void LCA(int u) { int len,i,j; ans[u]=u; len=tree[u].size(); for(i=0;i<len;i++) { LCA(tree[u][i]); Unionset(u,tree[u][i]); ans[find(u)]=u; } vis[u]=1; if ( ( u==s && vis[t]==1) ) printf("%d\n",ans[find(t)]); else if( (u==t && vis[s]==1) ) printf("%d\n",ans[find(s)]); } int main() { int T,i,j; int u,v; scanf("%d",&T); while(T--) { scanf("%d",&n); initial(); for(i=1;i<n;i++) { scanf("%d%d",&u,&v); tree[u].push_back(v); indegree[v]++; } scanf("%d%d",&s,&t); for(i=1;i<=n;i++) if( indegree[i]==0 ) { LCA(i); break; } } return 0; }