倍增
倍增字面意思上就是成倍的增长。我们在进行递推时,如果状态空间很大,通常的线性递推无法满足时间和空间复杂度的要求 ,那么我们就可以通过成倍的增长,只递推状态空间中在 2 的整数次幂位置上的值作为代表 。
快速幂
因为一个数我们是以二进制存储的,所以在快速幂的运用时,a的5次方。5转换成二进制为101,
那么a的5次方,就是a*(2^2加2^0)。可以这样转换。
ST表
ST 表是用于解决 可重复贡献问题 的数据结构。
即两个有重叠范围的区间求这两个区间合并起来的最大值,就等于两个区间分别的最大值的最大值。
RMQ求区间最值
即一种预处理时间复杂度为O(nlogn),但是查询的时间复杂度只要O(1)的算法。
也是运用了倍增的思想,求一个区间的最大值,我们就可以求这个区间里两个区间的范围加起来刚好覆盖这个区间的两个子区间的最大值的最大值。
我们需要先预处理
//预处理
//mx 最大值 mn 最小值
int mx[maxn][30],mn[maxn][30];
//a[i]表示原始输入数组
int a[i];
//Log数组表示的是log2 id的值
int Log[maxn];
//bin数组是表示2的i次方的值
long long int bin[40];
void init(int n) {
bin[0]=1;//2的0次方为1
for(int i=1; i<30; i++) { //将2的次幂都求出来 方便后面直接用
bin[i]=bin[i-1]*2;
}
Log[0]=-1;
for(int i=1; i<n+50; i++) { //求得每个数log以2为底的值
Log[i]=Log[i/2]+1;//当i=1时,Log[1]=log[0]+1=0; 因为log[0]不存在 所以用-1表示,方便得到log[1];
}
for(int j=1; j<=Log[n]; j++) {
for(int i=1; i<=n; i++) {
if(i+b[j]-1<=n) {//i的到i+2的j次方可以分为,区间 i到2的j-1次方和i+2的j-1次方到i+2的j次方。
mx[i][j]=max(mx[i][j-1],mx[i+b[j-1]][j-1]);
mn[i][j]=min(mn[i][j-1],mn[i+b[j-1]][j-1]);
}
}
}
}
预处理完,我们就可以得到i位置往后加上2的倍数的范围的区间的最值。
因为st表是可以求重复贡献问题的,所以我们可以吧需要求的区间,根据这个区间的大小划分一下,然后再求最值。
int search(int l,int r)//l和r范围为1~n
{
int k=Log[r-l+1];//r-l+1为区间大小
int t1=min(mn[l][k],mn[r-(1<<k)+1][k]);//区间最小值
int t2=max(mx[l][k],mx[r-(1<<k)+1][k]);//区间最大值
return t2-t1;
}
st表模板题 https://www.luogu.com.cn/problem/P3865
求区间最大值与最小值的差 https://www.luogu.com.cn/problem/P2880
倍增求lca
求lca也是运用倍增思想。
在朴素算法的基础上运用倍增,使他们快速在同一深度,然后此时退出循环的条件不再是祖先相等,而是,当遍历到两个人的祖先相等时最接近他们深度的这个祖先。退出循环。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=500000+50;
struct node{
int v;
int next;
}t[maxn*2];
int d[maxn],f[maxn][21],head[maxn];
//head[i]存放的是 值为i的结点的第一个儿子是t数组中的哪个,然后根据这个儿子可以遍历出其它的儿子
// d数组存放深度。f[i][j]表示值为i的结点的2的j次方级祖先
int k=0;
void add(int x,int y){//数组模拟链表 头插法
t[k].v=y;
t[k].next=head[x];
head[x]=k;
k++;
}
void dfs(int x,int fa)
{
d[x]=d[fa]+1;
f[x][0]=fa;//2的0次方为1 即x的一级祖先为fa
for(int i=1;(1<<i)<=d[x];i++){
f[x][i]=f[f[x][i-1]][i-1];//x的i级祖先等于 x的i-1级祖先的i-1级祖先(x的i次方等于x的i-1次方乘x的i-1次方)
}
for(int i=head[x];i!=-1;i=t[i].next){//遍历子树
int v=t[i].v;
if(v!=fa){
dfs(v,x);
}
}
}
int lca(int a,int b)
{
if(d[a]<d[b]){//先判断深度, 因为要先将两个数的深度一致
swap(a,b);
}
int h=d[a]-d[b];
for(int i=20;i>=0;i--){//大步跳,
if(h&(1<<i)){
a=f[a][i];
}
}
if(a==b) return b;//说明a就是b的祖先
for(int i=20;i>=0;i--){
if(f[a][i]==f[b][i]) continue;
a=f[a][i],b=f[b][i];
}
return f[b][0];
}
int main()
{
memset(head,-1,sizeof(head));
int n,m,s;
cin>>n>>m>>s;
int a,b;
for(int i=1;i<n;i++){
cin>>a>>b;
add(a,b);
add(b,a);
}
dfs(s,0);
for(int i=1;i<=m;i++){
cin>>a>>b;
cout<<lca(a,b)<<endl;
}
return 0;
}
最近公共祖先 https://www.luogu.com.cn/problem/P3379
找到的一个很棒的讲解视频https://www.bilibili.com/video/BV1nE411L7rz?from=search&seid=10858891541773824704
总结
学了倍增思想和st表,lca,明天打算看一看tarjan