树上倍增

文字转自:点击查看

倍增比起树链剖分,代码短,容易查错,时空复杂度也优很多(nlogn),只是功能有些欠缺。

倍增的思想是二进制。
        首先开一个n×logn的数组,比如f[n][logn],其中fa[i][j]表示i节点的第2^j个父亲是谁。

然后,我们会发现有这么一个性质:

                       f[i][j]=f[f[i][j-1]][j-1]

        用文字叙述为:i的第2^j个父亲 是i的第2^(j-1)个父亲的第2^(j-1)个父亲。

        这是不是很神奇?这样,本来我们求i的第k个父亲的复杂度是O(k),现在复杂度变成了O(logk)。

        我们知道,一个数的二进制形式中,如果右边数第i位上是1,表示这个数如果分解为若干个2的次幂的和的形式,其中有一项一定是2^(i-1)。举个例子:10的二进制表示为1010,它的第2位和第4位上是1,所以10=2^1+2^3。

        下面是求i的第k个父亲的代码段:

int father(int i,int k)
{
    for(int x=0;x<=int(log2(k));x++)
        if((1<<x)&k)    //(1<<x)&k可以判断k的二进制表示中,第(x-1)位上是否为1
            i=fa[i][x];     //把i往上提
    return i;
}


        这样讲应该很容易理解吧?
        我们可以通过一次dfs处理出fa数组:(dep[i]表示i的深度,这个可以一起处理出来,以后要用)

如果待处理的树有n个节点,那么最多有一个节点会有2^(logn)个父亲,所以我们的fa数组第二维开logn就够了。

这里用max0表示logn。初始化fa为0,若fa[i][j]=0表示i没有第2^j个父亲。

void dfs(int u) {
	for(int i = 0; i < m; i++)
		if(f[u][i - 1])
			f[u][i] = f[f[u][i - 1]][i - 1];
	for(int i = 0;i < v[u].size(); i++)
	{
		int to =v[u][i];
		if(to != f[u][0]) {
			f[to][0] = u;
			dep[to] = dep[u] + 1;
			dfs(to);
		}
	}
}


 
         这样,我们在nlogn的时间内可以通过一遍dfs处理出这棵树的相关信息。然后就可以在logn的时间内完成一些操作。
        倍增的应用中,最基础的应该就是求LCA(最近公共祖先),时间复杂度是logn。

        对于求u、v的LCA,我们可以先把u、v用倍增法把深度大的提到和另一个深度相同。如果此时u、v已经相等了,表示原来u、v就在一条树链上,直接返回此时的结果。

        如果此时u、v深度相同但不等,则证明他们的lca在更“浅”的地方,此时需要把u、v一起用倍增法上提到他们的父亲相等。为啥是提到父亲相等呢?因为倍增法是一次上提很多,所以有可能提“过”了,如果是判断他们本身作为循环终止条件,就无法判断是否提得过多了,所以要判断他们父亲是否相等。不懂的可以详见代码:

int LCA(int x, int y) {
	if(dep[x] < dep[y]) swap(x, y);
	int cnt = dep[x] - dep[y];
	for(int i = 0; i < m; i++)
		if((1 << i) & cnt)
			x = f[x][i];
	if(x == y) return x;

	for(int i = m; i >= 0; i--) {

		if(f[x][i] != f[y][i]) {
			x = f[x][i];
			y = f[y][i];
		}
	}
	return f[x][0];
}


        倍增还可以有很多变化,这让倍增法可以优更多的变化。比如用data[i][j]记录i到他的第2^j个父亲的路径长度,就可以边求LCA边求出两点距离,因为data[i][j]满足倍增的递推式:data[i][j]=data[i][j-1]+data[fa[i][j-1]][j-1]。或者用maxlen[i][j]记录i到第2^j个父亲的路径上最长边的边权,它满足maxlen[i][j]=max{maxlen[i][j-1],maxlen[fa[i][j-1]][j-1]},这样就可以快速求出两点路径上最长边的边权……

        总之,倍增是一种较为基础的处理树的方式,一定要熟练掌握,可以打几个模板题先做做,然后找一些经典的倍增习题。

猜你喜欢

转载自blog.csdn.net/mmk27_word/article/details/88422744