【学习笔记】倍增ST表、LCA学习笔记

学习笔记索引

众所周知,scy5赛时在P10059 Choose写了个滑动窗口骗 40 分,但是狂 WA 不止,丢掉了 rk155,于是就有了下面这两张口吐芬芳的图:

听说这题可以用ST表做,但他不会,于是他就来学倍增乐。

省流:被吊打了

更改日志

2024/01/16:开坑。

倍增原理

设做一件事有 n 个步骤。

模拟想法:我们可以一个一个步骤去做,时间复杂度 O(n)

有满足条件为:用第 x 步的结果可以推出第 2×x 步的结果。

那么我们可以预处理出 2 的幂次步的结果,然后将 n 二进制拆分。

n=7 时,等于第 1+2+4 步。

时间复杂度 O(logn),相较于模拟想法更优。

理解倍增

我们拿快速幂来当作例子(应该都会)。

大意

abmodp

0a,b<231a+b>02p<231

数据太大,模拟的时间复杂度为 O(b),不可行。

其实快速幂也可以用倍增优化。

可以发现,求幂次就满足倍增的条件:用第 x 步的结果可以推出第 2×x 步的结果。

因为 axa2×x 次方有关系,a2×x=ax×ax

然后就可以先求出 a1,a2,a3... 的值。

最后求 ab 时,将 b 二进制拆分。

那么,如 b=1313 可以分解为 8+4+1,那么答案就是 a8×a4×a1

二进制拆分应该都会,就不写了(懒)

二进制拆分就是算出 b 的二进制然后如果一位是1就拆出一个这一位相应的数啦!

ST表(RMQ)

P3865 【模板】ST 表

题目描述

给定一个长度为 N 的数列,和 M 次询问,求出每一次询问的区间内数字的最大值。

输入格式

第一行包含两个整数 N,M,分别表示数列的长度和询问的个数。

第二行包含 N 个整数(记为 ai),依次表示数列的第 i 项。

接下来 M 行,每行包含两个整数 li,ri,表示查询的区间为 [li,ri]

输出格式

输出包含 M 行,每行一个整数,依次表示每一次询问的结果。

提示

对于 30% 的数据,满足 1N,M10

对于 70% 的数据,满足 1N,M105

对于 100% 的数据,满足 1N1051M2×106ai[0,109]1liriN


解法

这题先考虑暴力,每次找一遍区间最大值,时间复杂度 O(n×m),不可能通过如此大的数据。

那么我们去想:这题是否满足倍增的条件:用第 x 步的结果可以推出第 2×x 步的结果?

答案是肯定的,因为我们可以知道,如果我们知道了两个相邻区间的最大值,那么这个大区间的最大值就是两者取 max,那么就可以知道,用两个相邻的长度为 2 的区间可以求出长度为 4 的区间,两个相邻的长度为 4 的区间可以求出长度为 8 的区间,以此类推……

那么定义一个数组 fi,j 表示从位置 i 开始,长度为 2j 的区间中的最大值是多少。

首先,所有的 fi,0=ai,因为就是从位置 i 开始,长度为 1 的区间中的最大值是多少,那肯定就是他自己。

然后,当 j>0 时,因为 2j=2j1+2j1,所以可以将长度为 2j 的区间拆为两个长度为 2j1 的区间,那么就转移就是(下面将 fi,j 写为 f(i,j)):

f(i,j)=max(f(i,j1),f(i+2j1,j1))

这样预处理的时间复杂度为 O(nlogn)

那么,预处理就好了。

然后是调用。

那么我们就对区间长度(就是 rl+1)进行二进制分解,设区间长度为 7,那么可以拆成 1+2+4,可以将区间 [2,8] 拆为三个区间 [2,2],[3,4],[5,8],这三个区间,分别对应 f(2,0),f(3,1),f(5,2)

然后二进制拆分即可。

CODE:

#include<bits/stdc++.h>
using namespace std;
int n,m,l,r,a[100010],ST[100010][20]; 
int qpow(int a,int b){//快速幂
	int ret=1;
	while(b){
        if(b%2!=0){
			ret=ret*a;
		}  
        a=a*a,b/=2;
    }return ret;
}int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		ST[i][0]=a[i];//初始化
	}for(int j=1;j<=20;j++){//枚举幂次
		for(int i=1;i+qpow(2,j)-1<=n;i++){//枚举起点
			ST[i][j]=max(ST[i][j-1],ST[i+qpow(2,j-1)][j-1]);//转移
		}
	}for(int i=1;i<=m;i++){
		scanf("%d%d",&l,&r);
		int len=r-l+1,ret=-1,cnt=0;
		for(int j=0;j<20;j++){
			if(len%2==1){//若这一位为一
				ret=max(ret,ST[l+cnt][j]);//那么就尝试更新
				cnt+=qpow(2,j);//下一个拆出的区间
			}len/=2;//去掉这一位
		}printf("%d\n",ret);
	}return 0;
}

咕咕咕

posted @   scy_qwq  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示