DP之倍增算法解决LCA问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Q1410136042/article/details/83307084

LCA,最近公共祖先,也就是树上两个节点的相同的祖先里距离它们最近的(也就是深度最深的)。倍增算法用于解决LCA问题的在线查询。

比如要找x和y的LCA,一般会怎么做?

首先是暴力。一种是DFS遍历说有点,无需预处理,查询是O(n)的。还有一种暴力是先一步一步将x和y提到同一高度,然后同时看x和y的父节点,相等则是LCA,不等则让x=father(x),y=father(y),这个查询的复杂度显然是跟x和y距离有关,平均复杂度也是线性的,即O(n)~~

这里要说的倍增是对第二种暴力的优化,O(1)的查询——额,看到将O(n)的查询优化成了O(1),就应该能想到O(n log n)的预处理~~~这里的倍增也不例外。

首先理解f[i][j]数组,f[i][j]表示的是结点i的第2^j(这是次方不是异或)代祖先的编号,这个数组是这个算法跟DP唯一的关系~~转移方程很容易就可以写出来:f[i][j] = f[ f[i][j-1] ][j-1],这很容易理解,因为2^j==2^{j-1} + 2^{j-1}理解这个数组后,预处理就好弄了~预处理主要就是求出每一个节点的深度和f[][]数组即可~~

首先,让x和y处于同一高度:因为x和y高度都已知了,只要求出它们的高度差,然后由深度更深的那个倍增查询直到高度相等即可。如果这不能理解的话,那就举个例子吧,假设x深度为3,y深度为8,高度差为5==2^2+2^0,也就是说只要依次令y=f[y][2],y=f[y][0]即可(这样先理解了思想,具体实现后面有代码)。

然后,此时x和y高度一样了,怎么求公共祖先呢?仍然是用倍增,每次使x=f[x][t],y=f[y][t]~这个t是最大的f[x][t] != f[y][t]的t。显然t具有单调递减的特性,所以一次for循环,即可求出最后的x和y——最后的x和y也就是最初x和y的所有处于同一深度的祖先里深度最小的不等的一对(这个不明白就自己想一下吧)。然后它们的父节点就是LCA了。

这个算法差不多讲完了,思想上就是只要先用倍增使x和y高度一样,然后使用倍增查找到最近公共祖先即可。

额,差点忘记了,这个算法是在线的,所以是支持修改的——允许树增加叶节点,只需要在加边的时候求出新节点高度,以及新节点的f数组即可~~

下面是基本的预处理+查询的代码,至于加边的就没写了(手动滑稽~~~)

/***************************************************************************
 *LCA,最近公共祖先的在线算法——倍增算法,常用于在线询问树上两点间的距离
 *即二者距离最近公共祖先的距离之和,以下代码用于解决这个问题
 ***************************************************************************/

struct Edge // 边,用于数组里
{
    int e, dis; // edge[i].e表示i与e之间有边,dis是i和e间的距离
    Edge(int ee=0, int diss=0):e(ee), dis(diss) {}
};
vector<Edge> edge[maxn];
int f[maxn][20];   //  f[i][j]表示i的第2^j代祖先
int dis[maxn][20]; //  dis[i][j]表示i到其第2^j代祖先的距离
int dep[maxn];     //  dp[i]表示i的深度

void lca(const int& rt)// nlog n建树,并得到每一个结点的深度,以及f和dis
{
    for(int i = 0; i < edge[rt].size(); ++ i)
    {
        const int& t = edge[rt][i].e, &d = edge[rt][i].dis;
        if(t != f[rt][0])//遍历每一个和rt相连的非父节点,即rt的子节点
        {
            f[t][0] = rt;   // 第一代祖先,即父节点
            dis[t][0] = d;  // 与父节点间的距离
            dep[t] = dep[rt]+1; // 比父亲深度大1
            for(int j = 1; (1<<j) < dep[t]; ++ j)
            {//求t的f和dis
                f[t][j] = f[f[t][j-1]][j-1];
                dis[t][j] = dis[t][j-1] + dis[f[t][j-1]][j-1];
            }
            //深度遍历rt的子树
            lca(t);
        }
    }
}

int queue(int x, int y)//查询节点x和节点y的距离
{
    if(dep[x] < dep[y])
        swap(x, y);//便于后面将它们提到同一高度

    int ret = 0;

    for(int i = 19; ~i; -- i)
    {// 让x,y处于同一高度,这个19是因为我f数组第二维开的20
        if(dep[f[x][i]] >= dep[y])
        {
            ret += dis[x][i];
            x = f[x][i];
        }
    }
    if(x != y)
    {// x != y 才继续找公共祖先,因为如果x==y,则表示y是x的祖先,它们的公共祖先就是y,不需要进一步寻找了
        for(int i = 19; ~i; -- i)
        {
            if(f[x][i] != f[y][i])
            {
                ret += dis[x][i] + dis[y][i];
                x = f[x][i];
                y = f[y][i];
            }
        }
        ret += dis[x][0] + dis[y][0];
        x = f[x][0];
        y = f[y][0];
    }

    return ret;//这里返回的是距离,如果只是要找祖先,只需要返回x或者y就可以了
}

猜你喜欢

转载自blog.csdn.net/Q1410136042/article/details/83307084