这篇文章是以前写的当时还用着pascal,还请见谅——蒟蒻mqy
倍增是一种思想,每次将考虑的范围扩大或减少一倍从而达到加速的效果,将某一步的O(n)优化到O(logn)
已经接触过的倍增:
快速幂(每次将指数缩小一倍)
归并排序(每次将排序区间缩小一倍)
倍增在st表上的实现:
St表又称稀疏表,f[I,j]是对于第i个点第2^j个状态的诠释
例题(RMQ问题-区间最值查询)
输入n,再输入n个数,进行m次询问,对于每次询问求出x,y之间的最小/最大值(n,m<=100000)
此时在不借助高级数据结构的情况下似乎没办法解决了,但其实可以通过倍增思想来将复杂度优化到(nlogn+m)
倍增在st表上的实现
以区间最大值为例
F[I,j]表示第i个数开始2^j个数当中的最大值
易得:
f[I,0]=a[i]
f[I,j]=min(f[I,j-1],f[i+2^(j-1),j-1])
这样做很明显是正确的,因为除2^0以外任何的2的幂都是偶数都可以分成已知的两部分
在求出f[I,j]后对于每一个大小不是2的整次幂的区间的RMQ有什么帮助呢?
这里给出一种O(1)回答每一个询问的方法,常规的st表的查询方法要O(logn)
K=trunc(ln(y-x+1)/ln(2))
ans=min(f[x,k],f[y-2^k,k])
最小值也类似
至此整个问题解决
LCA(最近公共祖先):
在对st算法有一定了解后,我们提出以下一个问题,并要求在O(nlogn)级别时间内实现
给定一颗树大小为n,进行m次查询操作,每一次操作请你求出x和y的最近公共祖先
如下图:4和6的最近公共祖先是2
LCA问题普通的查找对于每一次查询是最坏情况O(n)的,我们可以利用st表来进行预处理,在此提出两种st表求LCA的算法,在下且把它命名为:
1.基于RMQ的ST算法
2.不基于RMQ的ST算法
基于RMQ的ST算法:
在遍历一颗树的过程中,如果将DFS的轨迹记录下来为e[i],第i个节点第一次在e[i]中出现的位置为D[i]的话,节点x和节点y的LCA一定在E[D[x]..D[y]]中且
是深度最小的节点
证明:
如果x是y的LCA,那么一定是区间内深度最小的点
如果不是,他们的LCA一定要是x和y的祖先,
即向下遍历子树一定要涵盖x和y,而E[d[x]..d[y]]的过程是x向上回溯到LCA再遍历到y的过程,LCA即是当中深度最小的点
如图
E[i]=1,2,4,2,5,6,5,2,1,3,1
R[i]=0,1,2,1,2,3,2,1,0,1,0
D[i]=1,2,10,3,5,6
所以我们只需要通过一遍DFS求出所有的E[i],R[i]和D[i]再做一遍RMQ预处理后O(1)回答每一个询问即可
这样做虽然回答是O(1)的,但求DFS轨迹使n的大小变为了2n-1,n比较大的数据会TLE和MLE,要注意
不基于RMQ的ST算法:
上一种算法虽然对于m较大有优势但是在n较大时显得疲软,这里提供另一种思路,也对我们学习倍增思想有很大启发
F[I,j]表示第i个点的2^j的祖先
我们可以通过一遍DFS求出深度和F[I,0]
F[I,j]=f[f[I,j-1],j-1]
第i个节点2^j祖先等于第i个节点2^(j-1)的祖先的2^(j-1)个祖先{皆为已知量}
求出f[I,j]对我们查询LCA有什么帮助呢?
对于两个深度一样的点,我们只要用log时间枚举j,如果f[x,j]<>f[y,j]就将两个点都往上跳,否则不动
因为如果LCA是x,y的H个祖先,那么H一定可以分解成若干个不同的2的幂次之和
如果两个深度不同的点也可以用类似的方法将两个点深度变为相同
查询的代码:
int lca(int x,int y)
{
int ans=inf;
if(r[x]=0;j--)
if(r[f[x][j]]>=r[y])
{
ans=min(ans,s[x][j]);
x=f[x][j];
if(r[x]==r[y]) break;
}
if(x==y)return ans;
for(j=20;j>=0;j--)
if(f[x][j]!=f[y][j])
{
ans=min(ans,min(s[x][j],s[y][j]));
x=f[x][j]; y=f[y][j];
}
ans=min(ans,min(s[x][0],s[y][0]));
return(ans);
}
ST及LCA的实际题目应用:
noip2013day1T3货车运输
题目大意,在一张图上求m次任意两点之间的最大瓶颈路(n<=10000,m<=30000)
解决方法:构造最大生成树并将最大生成树的边放进邻接表里,对于每一对点只要求出他们的LCA和LCA路径上的最小值就好了
只需要在第二种LCA方法上求出
g[I,j]表示i到第j^2祖先路径上最小值就可以了g[I,j]=min(g[I,j-1],g[f[I,j-1],j-1])
开车旅行NOIP2012
给出n个排成一行的城市,每个城市有一个不同的海拔。定义两个城市间的距离等于他们的高度差的绝对值,且绝对值相等的时候海拔低的距离近,有两个人轮流开车,从左往右走。B每次都选最近的,A每次都选次近的。旅行时有一个总路程x,如果两个人的总路程>x或有一个人无法按照自己的原则选择目的城市,旅行就终止。
有两个问:
1.给出x0,求从哪一个城市出发,使得A走的路程/B走的路程最小。如果B走的路程=0,则比值视为无穷大。如果有多个城市满足要求,则输出海拔最高的那个城市。
2.给出x和s(出发城市),求旅行终止是A的路程和B的路程。
最主要的思路就是通过倍增来实现跳点(st)从而可以用O(nlogn)预处理O(logn)回答每一次询问
我们把小A开一天小B开一天压缩成一步
f[i,j]表示从第i个点开始开2^j步所到达的点,转移方程基本雷同lca的样子f[i,j]=f[f[i,j-1],j-1]
光有这个还不够,我们还需要知道对于每一个f[i,j]小A开的路程和小B开的路程
AA[i,j]=AA[i,j-1]+AA[f[i,j-1],j-1]
BB[i,j]=BB[i,j-1]+BB[f[i,j-1],j-1]
接下来就是预处理了
想要求出AA[i,0],BB[i,0]和f[i,0]必须求出每一个点往前开的最近点和次近点fi[i]和se[i]
可以通过排序加双向链表求得
则f[i,0]=fi[se[i]]
AA[i,0]=|w[i]-w[se[i]]|
BB[i,0]=|w[se[i]]-w[fi[se[i]]]|
注意此处n-1和n不能预处理因为会无法到达。
对于第一问,枚举起点用logn时间算出小A和小B的路程并打擂
这里也是类似LCA的倒着扫并更新当前点u
第二问也类似于第一问
总结:
倍增是一种思想而不是一种固定的算法,不能死记硬背,要巧妙的灵活运用,虽然看来讲到的只有LCA和RMQ但是可以演变出千千万万种题目,唯有深刻理解和多练习才能做好倍增的题目