倍增小总结
因为考试倍增题挂了一个long long,被迫写总结QAQ
●何谓倍增法
简单的说就是每次步数是原来的两倍,一般用数组f[i][k]表示在i这个点走了2^k步之后的结果。
●倍增法的用处?
倍增法多用于数列或者树上,进行一段较大的区间查询,把每次查询的时间由O(n)降到O(logn)。
●倍增法的常见应用
一、RMQ 算法
这个算法其实可以说是个dp了。
1.简单介绍
大致是在一段很长序列上进行区间查询,用st[i][k]表示从i开始后2^k个数中的最大/小值或者和。这个数组叫做st表,建立的话是O(nlogn)的时间,但查询只需要O(1)。
2.对比数据结构
相比于线段树来说,不仅代码更好写,并且时间更优,因为线段树常数大且每次操作都是logn。但线段树可以支持很多操作,还可以修改之类的,就很屌了。
比上树状数组么。。
树状数组常数也很小,并且支持修改操作,但它有一个致命的BUG,那就是它只能查询与前缀相关的东西,譬如前缀和之类的。
如果查询区间和,直接用前缀和减去就好了,但是如果查询区间最大值,那么它就没有任何办法了。
3.简单建立st表
确实很简单,大致参照一个dp思路
i~i+2^k的区间信息可由i+2^(k-1)~i+2^(k-1)+2^(k-1)和i~i+2^(k-1)合并得到。st数组定义参照介绍。
for(int k=1;(1<<k)<=n;k++) for(int i=1;i+(1<<j)-1<=n;i++) st[i][k]=min(st[i][k-1],st[i+(1<<(k-1))][k-1]);
值得注意的是,由于处理2^k区间长度时要用到i和i+2^(k-1)点的信息,所以先枚举k。
4.查询没什么好提的,根据定义来就行
int rmq(int a,int b){
if(a>b)swap(a,b);
int t=(int)log2(b-a+1);
return min(st[a][t],st[b-(1<<t)+1][t]);
}
5.简单的小优化
由于上述的2^i和log2(i)都需要计算,而log2函数的常数是比较大的,所以可以根据幂和log的性质预处理出mi[]和lg[]数组。
此外,RMQ算法在后缀数组中用的还是比较多的。
二、树上倍增
1.寻找爸爸
树上倍增这个东西呢,一般是用来求lca的,单次查询时间logn,具体的描述有点长,就不写了。
还是大致提一下。关键是建立fa[i][k]表示i点向上数2^k个父亲是谁。查询lca(x,y)的时候得到x,y的深度,帮它们两个提到同一深度,再同时向上提找爸爸就好了。具体有些细节不再赘述。
挂一波代码。
void dfs(int u,int dad,){ p[u][0]=dad; d[u]=d[dad]+1; for(int i=1;(1<<i)<=d[u];i++) p[u][i]=p[p[u][i-1]][i-1]; for(int i=hd[u];i;i=e[i].next) if(e[i].v!=dad)dfs(e[i].v,u,e[i].w); } int lca(int x,int y){ if(d[x]>d[y])swap(x,y); for(int i=20;~i;i--) if(d[x]<=d[y]-(1<<i))y=p[y][i]; if(x==y)return x; for(int i=20;~i;i--){ if(p[x][i]==p[y][i])continue; x=p[x][i];y=p[y][i]; } return p[x][0]; }