倍增
倍增是把时间复杂度为
O
(
n
)
O(n)
O(n)的一个操作变为
O
(
log
2
n
)
O(\log_2n)
O(log2n)的操作。
它与分治的思想类似,时间复杂度也类似。
它们的区别如下:
分治是将一个问题分成若干个子问题,最后的答案由子问题的答案合并得到。
倍增是将一个需要执行多次的操作分解,操作的结果直接由两个子操作的结果得到。
例子
最常见的例子就是在树上倍增。
每个询问要求节点
x
x
x向上跳
s
s
s次到达的节点编号。
若每次暴力向父亲节点跳,一共跳
s
s
s次,时间复杂度特别大。
这时就要使用倍增。
如何实现
维护每个节点向上跳可到达的节点编号。
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示节点
i
i
i向上跳
2
j
2^j
2j次到达的节点编号。
所有的
f
[
i
]
[
0
]
f[i][0]
f[i][0]自然就为
f
a
t
h
e
r
[
i
]
father[i]
father[i](
i
i
i的父亲节点),其余的如何转移?
我们可以发现,向上跳
2
j
2^j
2j次就相当于是先向上跳
2
j
−
1
2^{j-1}
2j−1次,再向上跳
2
j
−
1
2^{j-1}
2j−1次。
于是我们不难得到转移方程:
f
[
i
]
[
j
]
=
f
[
f
[
i
]
[
j
−
1
]
]
[
j
−
1
]
f[i][j]=f[f[i][j-1]][j-1]
f[i][j]=f[f[i][j−1]][j−1]
void dfs(int i,int fa)
{
f[i][0]=fa;
for(j=1;j<=log(n);j++) f[i][j]=f[f[i][j-1]][j-1];
for(j=last[i];j;j=next[j]) if(a[j]!=fa) dfs(a[j],i);
}
怎么跳?
类似十进制转二进制的方式,从大到小枚举
j
j
j,如果
s
≥
2
j
s≥2^j
s≥2j就把
x
x
x跳到
f
[
x
]
[
j
]
f[x][j]
f[x][j],并且把
s
−
2
j
s-2^j
s−2j。
for(j=log(n);j>=0;j--)
{
if(s>=p[j])
{
x=f[x][j];
s-=p[j];
}
}
总结
可以用倍增实现的还有很多,如树上最大值、树上两点的最近公共祖先(LCA)等,对解题有很大帮助。
相信你已经对倍增了解不少了。要时刻记得,算法是死的而人是活的,必须学会灵活变通,找到题目突破口,才能顺利解题!