浅谈算法-简单倍增及st表的应用

       这篇文章是以前写的当时还用着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次查询操作,每一次操作请你求出xy的最近公共祖先

如下图:46的最近公共祖先是2

浅谈算法-简单倍增及st表的应用_第1张图片

LCA问题普通的查找对于每一次查询是最坏情况O(n)的,我们可以利用st表来进行预处理,在此提出两种st表求LCA的算法,在下且把它命名为:

1.基于RMQST算法

2.不基于RMQST算法


基于RMQST算法:

在遍历一颗树的过程中,如果将DFS的轨迹记录下来为e[i],i个节点第一次在e[i]中出现的位置为D[i]的话,节点x和节点yLCA一定在E[D[x]..D[y]]中且

是深度最小的节点

证明:

如果xyLCA,那么一定是区间内深度最小的点

如果不是,他们的LCA一定要是xy的祖先,

 即向下遍历子树一定要涵盖xy,而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比较大的数据会TLEMLE,要注意


不基于RMQST算法:

上一种算法虽然对于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]就将两个点都往上跳,否则不动

因为如果LCAx,yH个祖先,那么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);
}


STLCA的实际题目应用:

noip2013day1T3货车运输

题目大意,在一张图上求m次任意两点之间的最大瓶颈路(n<=10000,m<=30000)

解决方法:构造最大生成树并将最大生成树的边放进邻接表里,对于每一对点只要求出他们的LCALCA路径上的最小值就好了

只需要在第二种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.给出xs(出发城市),求旅行终止是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-1n不能预处理因为会无法到达。

对于第一问,枚举起点用logn时间算出小A和小B的路程并打擂

这里也是类似LCA的倒着扫并更新当前点u

第二问也类似于第一问


总结:

倍增是一种思想而不是一种固定的算法,不能死记硬背,要巧妙的灵活运用,虽然看来讲到的只有LCARMQ但是可以演变出千千万万种题目,唯有深刻理解和多练习才能做好倍增的题目

 

你可能感兴趣的:(算法,模板)