LCA 与 RMQ

求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_第1张图片

下面借用了别人的总结展示一下各个算法的时间复杂度:

 

LCA 与 RMQ_第2张图片

所以说,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;
}

你可能感兴趣的:(LCA 与 RMQ)