倍增法

wuyoudexian / 2023-08-04 / 原文

通常计算\([i,n]\)区间的所有情况的时间复杂度为\(O(n^2)\),而用倍增法只需\(O(nlogn)\)的复杂度就能表示出\([i,n]\)区间的所有情况。

求第i的下2^j个节点

当给出的数据中任意元素只能单向通向令一个唯一元素时,设\(fa[i][j]\)为第\(i\)个元素的下\(2^j\)个节点,可以得到以下递推公式。

for(int j = 1; j < 31; j++) {//j的大小根据数据范围确定
	for(int i = 1; i <= n; i++) {
    	fa[i][j] = fa[fa[i][j - 1]][j - 1];
    }
}

维护区间值

\(mul[i][j]\)为区间\(i\)\(i+2^j\)所有元素的乘积,可以得到以下递推公式。

for(int j = 1; j < 31; j++) {
	for(int i = 1; i <= n; i++) {
		mul[i][j] = mul[i][j - 1] * mul[fa[i][j - 1]][j - 1];
	}
}

\(add[i][j]\)为区间\(i\)\(i+2^j\)所有元素之和,可以得到以下递推公式。

for(int j = 1; j < 31; j++) {
	for(int i = 1; i <= n; i++) {
		add[i][j] = add[i][j - 1] + add[fa[i][j - 1]][j - 1];
	}
}

从上面两个例子可以看出,区间\([i,i+2^j]\)的维护值是从\([i,i+2^{j-1}]\)\([i+2^{j-1},i+2^j]\)推出来的。

查询区间值

以上面两个例子为例

查询区间\([l,r]\)乘积

for(int j = 30; j >= 0; j--) {
	if(r >= (1 << j)) {
		r -= (1 << j);
		ans *= mul[l][j];
        l = fa[l][j];
	}
}

查询区间\([l,r]\)之和

for(int j = 31; j >= 0; j--) {
	if(r >= (1 << j)) {
		r -= (1 << j);
		ans += add[l][j];
		l = fa[l][j]
	}
}

可以看出,每次查完区间\([l,l+2^j]\),把\(l\)移到\(l+2^j\)处,再进行下一次查询。