求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;
}