我所了解的倍增算法
我所了解的倍增算法大体是这样的,当我们想知道2^n次方这么个长度范围内的某个值(如最大值或者最小值),如果我知道了所有的2^(n-1)次方长度范围内的值,这样我就可以通过我所知道的这些值中算出我要的值。具体在定义的时候一般是这样的
anc[i][k] 表示以i开头,长度为2^k次方的这么一个区间我们所要的值(这里以最小值为例),那么当我知道一个数组的所有数之后,我就可以用nlogn的时间来进行预处理,然后可以用logn的时间来获得任意一个区间的最小值
具体可以这样来做
for (int i=1;i<=n;i++) anc[i][0] = a[i]; for (int k=1;k<=20;k++) for (int i=1;i<=n;i++) anc[i][k] = min(ans[i][k-1], ans[i + (1<<(k-1))][k-1]);
因为我们知道任意一个区间都可以有2的多个次方组成,故可以用多个2^k来组成一个区间,这样就可以在logn的时间求出我们想要的值
倍增算法的应用
1,在求最大值及最小值上的应用
正如上面所说的,倍增算法可以用来求最小值,同时也可以用来求最大值,只要把min改成max就可以了,这也是所说的RMQ算法
2,用来求LCA
LCA就是最近公共祖先,就是一棵树,求两个结点的最近公共祖先,我们也可以像刚才的代码一样预处理出anc数组
for (int i=1;i<=n;i++) anc[i][0] = fa[i]; for (int k=1;k<=20;k++) for (int i=1;i<=n;i++) anc[i][k] = min(ans[i][k-1], ans[i + (1<<(k-1))][k-1]);
只是预处理的时候距离为1的祖先应该是其父亲结点,然后在求两个结点的最近公共祖先时,先把深度的结点上升到和深度较小的结点一样的深度,具体的上升方法也用anc这个数组来升,就是把u节点上升到第H个祖先的位置,即长度为H,起始位置为u,然后在应用anc数组,找到两个结点的Lca,具体看下面代码
上升 void swim(int &x,int H){for (int i=0;H>0;i++){if (H&1) x=anc[x][i]; H/=2; }} 获得LCA int Lca(int u,int v) { int i; if (dep[u]>dep[v]) swap(u,v); //printf("%d %d\n",u,v); swim(v,dep[v]-dep[u]); //printf("%d\n",v); if (u==v) return u; for (; ;) { for (i=0;anc[u][i]!=anc[v][i];i++); //printf("%d %d %d %d\n",u,anc[u][i],v,anc[v][i]); if (i==0) return anc[u][0]; u=anc[u][i-1]; v=anc[v][i-1]; } return -1; }
3,扩展应用
可以看出,只要是满足结合律,我们都可以用这个方法来求,如gcd(最大公约数),没想到吧,一个区间的最大公约数也可以这样来求,其他的只要满足结合律关系的,在对区间询问时,都可以考虑一下这样的算法
4,换位思考后的应用
我们一般都是考虑区间里面的某个值,可不可以倒过来,考虑值,然后存区间呢?
曾经碰到过这样一个题:
X轴上有很多区间,现在有m个询问,给你一个区间[L,R],问这个区间内最多有多少个不重复的区间
这个题就可以考虑倍增算法,anc[i][k]表示以i开始,2^k次方个不想交的区间的最右边的位置的最小值,于是以同样的做法,稍加处理就可以得到任意一个区间的答案
以上便是我给出的几个方面的应用,当然1,2都是显然的,而3,4都是要求结合实际问题来讨论的