浅谈倍增算法
倍增(ST)
倍增是什么?
倍增,每次将范围扩大或减少一倍以达到加速的效果
思想引入:如果你想跳到n = 15米远的地方,怎么做?
-
一步一步跳过去(暴力):显然这是15次
-
\(2^k\)次(倍增):
- 设 k = 5,\(2^5\) = 32 > n, k -- ,k = 4 (超了)
- k = 4, \(2^4\) = 16 > n, k --,k = 3 (超了)
- k = 3,\(2^3\) = 8 < n,n = n - 8 = 7, k --, k = 2(跳)
- k = 2,\(2^2\) = 4 < n,n = n - 4 = 3, k --, k = 1(跳)
- k = 1,\(2^1\) = 2 < n,n = n - 2 = 1, k --, k = 0(跳)
- k = 0,\(2^0\) = 1 < n,n = n - 1 = 0(跳到了)
即只跳了4次,与暴力做法相比,少了11次。而且数据越大相差的越大,因为暴力是O(n)而倍增是O(\(log_n\))
可以发现一个性质,如果跳用1,不跳用0表示,则用二进制表示为1111,这恰好是15。
所以这就称为
二进制转换,比如11用倍增的思想做出来,恰好是1011.
在图论中的应用
在大多数情况下,图论中的点或边都可以按某种方式排序,如果问题要求的是O(\(n log_n\))的时间复杂度,就需要考虑倍增的思想,可以结合\(dp\)的最优子结构和ST表的思想求解。
倍增在LCA(最近公共祖先)中的应用
LCA其实就相当于在树上找\(u到v\)的最短路,因为找到了最近公共祖先就相当于找到了一条最短路径
首先,要找到\(u\)和\(v\)第一个不同祖先不同的位置,然后从这个位置向上走一步就是最近公共祖先。要想找到\(u,v\)第一个不同祖先的位置,就要保证\(u,v\)在同一深度(这样才能同时向上移一步)
所以这个过程分为三步:
1. 预处理找到每个节点深度
2. 把较深的一点移动到与较浅一点的高度
3. 两个一起往上移动直到它们的父亲节点相同
先用一个dfs/bfs找到所有的节点深度,用depth数组存下
fa[i,j]表示从\(i\)开始,向上走\(2^j\)步所能走到的节点。
预处理
void bfs(int root)
{
memset(depth,0x3f,sizeof depth);
depth[0] = 0,depth[root] = 1;
int hh = 0,tt = 0;
q[0] = root;
while(hh <= tt)
{
int t = q[hh ++];
for (int i = h[t];~i;i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[ ++ tt] = j;
fa[j][0] = t;
for (int k = 1;k <= 15;k ++)
fa[j][k] = fa[fa[j][k - 1]][k - 1];
}
}
}
}
查询
int lca(int a,int b)
{
if (depth[a] < depth[b]) swap(a,b);
for (int k = 15;k >= 0;k --)
if (depth[fa[a][k]] >= depth[b])
a = fa[a][k];
if (a == b) return a;
for (int k = 15;k >= 0;k --)
if (fa[a][k] != fa[b][k])
{
a = fa[a][k];
b = fa[b][k];
}
return fa[a][0];
}

浙公网安备 33010602011771号