ST算法 - RMQ(区间最值问题)—— 倍增
文章目录
- 引入倍增:
- 例题1:区间和
- 例题2:Genius ACM
- 应用:
- ST算法 求解 RMQ(区间最值问题)
- 模板Code:
- 练习题:
- ST算法 维护区间最大公约数
- 例题:Pair of Numbers
引入倍增:
所谓倍增,就是成倍增长。以2的次幂的方式增长。
我们在进行递推时,如果状态空间很大,线性递推无法满足时间与空间复杂度的要求,我们可以通过“成倍增长”的方式,只递推在2的整数次幂位置上的值作为代表。
当需要其他位置上的值时,也可以用这些2的幂次上的值所拼成,因为 “任意整数都可以表示成若干个2的次幂项的和”。
例题1:区间和
题目描述:
给定长度为 n n n 的数列,进行若干次询问。
给出整数 T T T,给出左端点 p p p,求出最大的 k k k,使得从 l l l 开始的 k k k 个位置元素之和不超过 T T T。
思路:
预处理出前缀和 s [ i ] s[i] s[i]。
考虑暴力算法,依次往后枚举 k k k 的位置,时间复杂度 O ( N ) O(N) O(N)。
由于前缀和满足单调性,所以可以二分 k k k 的位置。
但是,对于每次询问,二分的时间复杂度都为 O ( N l o g N ) O(NlogN) O(NlogN)。如果当答案 k k k 很小的话,还不如直接枚举效率高。
那么是否找到一种方法,能够兼顾两者的优点呢?
倍增!
我们可以用2的幂次来判断 k k k 的位置。设立左端点 l = p l = p l=p,右端点 r = 1 r =1 r=1,倍增长度 l e n = 1 len =1 len=1。
- 如果
s[r+len] - s[l-1]≤ T
,说明当前长度可行,继续倍增,r+=len
,len*=2
; - 否则,说明倍增长度太长,就要缩减,
len/=2
。
重复上述操作,直到 l e n = 0 len=0 len=0了,那么当前 r r r 便是答案。
这样,如果答案 k k k 很小,这个算法的复杂度便也变小。
这个算法始终在答案大小的范围内实施“倍增”与“二进制划分”思想,通过若干长度为2的次幂的区间拼成最后的 k k k,时间复杂度级别为答案 k k k 的对数,能够应对 T T T 的各种大小的情况。
例题2:Genius ACM
题意:
给定一个整数 M M M,对于任意一个整数集合 S S S,定义“校验值”如下:
从集合 S S S 中取出 M M M 对数(即 2 ∗ M 2*M 2∗M个数,不能重复使用集合中的数,如果 S S S 中的整数不够 M M M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合 S S S 的“校验值”。
现在给定一个长度为 N N N 的数列 A A A 以及一个整数 T T T。我们要把 A A A 分成若干段,使得每一段的“校验值”都不超过 T T T。
求最少需要分成几段?
思路:
对于一个集合 S S S,为了使“每对数的差的平方”之和最大,只能最大值和最小值配对,次大值和次小值配对…
为了总的段数最小,需要让每一段的“检验值”不超过T的前提下尽量长。所以从从头开始对 A A A 分段,让每一段都尽量长,这样得到的就是最小分段数。
于是,需要解决的问题是,对于一个起点 l l l,最多往后延伸多少个位置,能够使得这一段区间的“检验值”不超过 T T T?
因为往后延伸的区间越长,其“检验值”越大,满足单调性,所以很容易想到二分右端点。
但是对于每一次二分,复杂度为O(logN),对于每一次check,需要排序O(NlogN),而最坏情况下,需要对每个位置二分右端点,所以整个复杂度为 O ( N 2 l o g 2 N ) O(N^2 log^2N) O(N2log2N)。(其实真实是O(N^2 logN),证明)复杂度很高。
而用倍增,复杂度可以降到 O ( N l o g 2 N ) O(N log^2N) O(Nlog2N)。
对于一个起点位置 l l l,定义右端点 r = l r=l r=l,倍增长度 l e n = 1 len=1 len=1。
- 如果区间 [ l , r + l e n ] [l, r+len] [l,r+len] 的“校验值”满足,那么说明当前倍增的长度是可以的,更新右端点
r+=len
,len*=2
; - 否则,说明倍增长度太长,
len/=2
。
重复上述操作,直到倍增长度 l e n = 0 len =0 len=0 ,此时的 r r r 便是最右端的位置。
考虑这种算法的复杂度:
上面的过程最多循环 O ( l o g N ) O(logN) O(logN) 次,每次循环求“检验值” O ( N l o g N ) O(N logN) O(NlogN),所以时间复杂度为 O ( N l o g 2 N ) O(N log^2N) O(Nlog2N)。
Code:
const int N = 500010, mod = 1e9+7;
ll T, n, m, a[N],b[N];
ll maxa;bool pd(int l,int r){if(r>n) return 0; //最右端不超过数组长度 int cnt=0;for(int i=l;i<=r;i++) b[++cnt]=a[i];sort(b+1,b+cnt+1);ll sum=0;for(int i=0;i<m;i++){if(i+1>=cnt-i) break;sum+=(b[cnt-i]-b[i+1])*(b[cnt-i]-b[i+1]);}if(sum<=maxa) return 1;return 0;
}signed main(){Ios;cin>>T;while(T--){int cnt=0;cin>>n>>m>>maxa;for(int i=1;i<=n;i++) cin>>a[i];int st=1;while(st<=n){ll l=st,r=st,len=1; //设置左端点,右端点,倍增长度 while(len!=0) //当倍增长度为0的时候结束 {if(pd(l,r+len)) r+=len,len*=2; //满足,倍增 else len/=2; //不满足,倍减 }st=r+1;cnt++;}cout<<cnt<<endl;}return 0;
}
对于每次求“检验值”,可以不用 s o r t sort sort 排序,而是采用归并排序,只对新增的长度排序,然后合并新旧两段,总体复杂度可以降到 O ( N l o g N ) O(N logN) O(NlogN)。
应用:
ST算法 求解 RMQ(区间最值问题)
RMQ问题:
给定一个长度为 n n n 的数列,每次给出一个区间,问这个区间中元素的最大值?
对于暴力,时间复杂度为 O ( N ∗ M ) O(N*M) O(N∗M), M M M 为询问次数。
而 S T ST ST 算法能在 O ( N l o g N ) O(N logN) O(NlogN) 时间的预处理之后,以 O ( 1 ) O(1) O(1) 的时间复杂度在线回答 R Q M RQM RQM 问题。
定义 f [ i , j ] f[i,j] f[i,j] 表示数列中下标在区间 [ i , i + 2 j − 1 ] [i, i+2^j-1] [i,i+2j−1] 里的数的最大值,也就是从位置 i i i 开始的 2 j 2^j 2j 个数的最大值。
递推求出 f [ i , j ] f[i,j] f[i,j]: O ( N l o g N ) O(N logN) O(NlogN)
递推边界: f [ i ] [ 0 ] = a [ i ] f[i][0] = a[i] f[i][0]=a[i],即数列a在子区间 [ i , i ] [i,i] [i,i] 里的最大值。
递推时,我们把子区间的长度成倍增长,长度为 2 j 2^j 2j 的子区间的最大值为左右两半长度为 2 j − 1 2^{j-1} 2j−1 的子区间的最大值中较大的一个,即:f[i,j] = max(f[i, j-1], f[i + (1<<(j-1)),j-1]
。
考虑 j j j 的最大值,为使得 2 j 2^j 2j 不超过 n 的最大的 j,那么 j = l o g 2 n j = log_2^n j=log2n。
我们可以调用 < c m a t h > <cmath> <cmath>中的 log()
函数, l o g 2 n = l o g ( n ) / l o g ( 2 ) log_2^n = log(n)/log(2) log2n=log(n)/log(2)。 ( l o g 2 n = l o g 10 n / l o g 10 2 ) (log_2^n = log_{10}^n / log_{10}^2) (log2n=log10n/log102)。
考虑 i i i 的最大值,从 i i i 往右延伸的区间长度最大为 2 j 2^j 2j ,所以 i i i 最大只需要到 n − 2 j + 1 n-2^j+1 n−2j+1。
递推时,当前状态需要用到 前面 j − 1 j-1 j−1 状态的 i + 2 j − 1 i+2^{j-1} i+2j−1 ,所以需要先循环 j j j,再循环 i i i。
void RMQ()
{for(int i=1;i<=n;i++) f[i][0]=a[i];int t=log(n)/log(2); //t为 不超过n的,2^t的最大值 = log_2^n。 for(int j=1;j<=t;j++) //先遍历j,再遍历i。 {for(int i=1;i<=n-(1<<j)+1;i++) //i位置最大为 n-2^j+1。{f[i][j]=max(f[i][j-1],f[i + (1<<(j-1))][j-1]);//max(最区间最大值,右区间最大值)}}
}
对于询问一个区间 [ l , r ] [l,r] [l,r] 的最大值: O ( 1 ) O(1) O(1)
我们需要先算出不超过这个区间长度的 2 t 2^t 2t 的 t t t 的最大值: l o g 2 r − l + 1 log_2^{r-l+1} log2r−l+1。
那么这个区间的最大值就为 “从 l l l 开始的 2 t 2^t 2t 个数” 和 “以 r r r 结尾的 2 t 2^t 2t 个数” 这两段的最大值较大的一个。即 max(f[l][t], f[r-(1<<t)+1][t])
。
int query(int l,int r){int t=log(r-l+1)/log(2); //这里是区间长度的对数,不是整个数组的对数 return max(f[l][t],f[r-(1<<t)+1][t]); //从后往前找的时候+1,从前往后不用加。
}
模板Code:
#include<iostream>
#include<cmath>
using namespace std;const int N=100010;
int n,m,a[N];
int f[N][20];void RMQ()
{for(int i=1;i<=n;i++) f[i][0]=a[i];int t=log(n)/log(2); for(int j=1;j<=t;j++) for(int i=1;i<=n-(1<<j)+1;i++)f[i][j]=max(f[i][j-1],f[i + (1<<(j-1))][j-1]);
}int query(int l,int r){int t=log(r-l+1)/log(2);return max(f[l][t],f[r-(1<<t)+1][t]);
}int main(){cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];RMQ();while(m--){int x,y;cin>>x>>y;cout<<query(x,y)<<endl;}return 0;
}
同理,把 m a x max max 换成 m i n min min ,我们可以求出一个区间的最小值。
练习题:
1、数列区间最大值
2、最敏捷的机器人
ST算法 维护区间最大公约数
和区间最值有相似性质的还有 最大公约数 g c d gcd gcd。
类似于更新区间最值的方法,对于一整个区间的 gcd 等于其两个子区间的 gcd 的 gcd。要求两个子区间覆盖住整个区间,允许有重合。
所以,和维护最值一样:
- 在更新的时候用两个半长区间更新;
- 查询的时候用两个2的次幂数长度的区间取 gcd。
void st(){int t = log(n)/log(2);for(int j=1;j<=t;j++)for(int i=1;i<=n-(1<<j)+1;i++){gcd[i][j] = __gcd(gcd[i][j-1], gcd[i+(1<<(j-1))][j-1]);}
}int query(int l, int r)
{int t = log(r-l+1)/log(2);return __gcd(gcd[l][t], gcd[r-(1<<t)+1][t]);
}
例题:Pair of Numbers
题意:
给定长度为 n 的数列,求出长度最长的满足下列条件的区间:
- 区间中存在一个数 x,能够被其他所有数除尽。
1 ≤ n ≤ 3 ∗ 1 0 5 1 ≤ n ≤ 3*10^5 1 ≤ n ≤ 3∗105
分析:
如果长度为 5 的区间满足上面条件的话,那么其长度为 3 的子区间也一定满足。所以区间长度满足单调性,可以二分最长长度。
对于每种长度,遍历起点位置。
如果说,区间所有数中的最小值等于这些数的最大公约数的话,那么这个最小值就能够被其他所有数除尽,就满足条件。
用ST表维护区间 gcd 和区间最小值,O(1) 查询。
Code:
#include<bits/stdc++.h>
using namespace std;#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<int,int> mp;/**/const int N = 300010, mod = 1e9+7;
int T, n, m;
int a[N];
int mina[N][30], gcd[N][30];
vector<int> ans[N];void st(){int t = log(n)/log(2);for(int j=1;j<=t;j++){for(int i=1;i<=n-(1<<j)+1;i++){mina[i][j] = min(mina[i][j-1], mina[i+(1<<(j-1))][j-1]);gcd[i][j] = __gcd(gcd[i][j-1], gcd[i+(1<<(j-1))][j-1]);}}
}bool query(int l, int r)
{int t = log(r-l+1)/log(2);int minn = min(mina[l][t], mina[r-(1<<t)+1][t]);int gcdd = __gcd(gcd[l][t], gcd[r-(1<<t)+1][t]);return minn == gcdd;
}bool check(int mid)
{int flag = 0;for(int i=1;i<=n-mid+1;i++){if(query(i, i+mid-1)){flag = 1;ans[mid].pb(i);}}if(flag) return 1;return 0;
}signed main(){cin >> n;for(int i=1;i<=n;i++) cin>>a[i], mina[i][0] = gcd[i][0] = a[i];st();int l = 0, r = n;while(l<r){int mid = l+r+1>>1;if(check(mid)) l = mid;else r = mid-1;}cout << ans[l].size() << " " <<l-1<<endl;for(auto x:ans[l]) cout<<x<<" ";return 0;
}
那么既然这样的话,或操作(|) 和 与操作(&) 也可以区间维护了。
参考来源: 《 算 法 竞 赛 进 阶 指 南 》 — — 李 煜 东 《算法竞赛进阶指南》 ——李煜东 《算法竞赛进阶指南》 ——李煜东
哪里有问题或者不明白的话欢迎留言评论~
ST算法 - RMQ(区间最值问题)—— 倍增相关推荐
- RMQ的ST算法(区间最值)
ST算法求解RMQ问题(区间最值) 效率:O(n log n)预处理,O(1)询问 思想: 用 f [ i ][ j ] 表示 以i 开头的区间,包括2^j 个元素的一段区间的最值 那么有初始化的初始 ...
- st算法 求区间最值问题
算法的一个实现方法如下. 其中使用位运算替代2的幂次的计算,加快运算速度. 使用时需要先调用initRMQ()进行初始化,然后再调用RMQ(u,v)进行查询. 1 const int mx = 100 ...
- 疯子的算法总结14--ST算法(区间最值)
借助倍增和动态规划可以实现O(1)的时间复杂度的查询 预处理: ①区间DP 转移方程 f[i][j] = min(MAX同理)(f[i][j - 1],f[i + ][j - 1]) f[i] ...
- 数据结构:线段树及ST算法比较
ST算法是一种高效的计算区间最值的方法. 他的思想是将询问区间分解成两个最长的二次幂的长度的区间并集的形式. 所以与线段树不同,这种区间分解其实存在相交的分解. 因此ST算法能维护的只是一些简单的信息 ...
- RMQ问题-ST表倍增处理静态区间最值
简介 ST表是利用倍增思想处理RMQ问题(区间最值问题)的一种工具. 它能够做到O(nlogn)预处理,O(1)查询的时间复杂度,效率相当不错. 算法 1.预处理 ST表利用倍增的思想.以洛谷的P38 ...
- 1470: 区间求最值(RMQ问题,ST算法模板)
1470: 区间求最值 Time Limit: 1 Sec Memory Limit: 128 MB [Submit][Status][Web Board] Description 给定一个长度为N ...
- RMQ算法,求区间最值
poj 3264 Balanced Lineup@ 2016-07-27 11:15 49人阅读 评论(0) 收藏 举报 分类: RMQ(Range MinimumMaximum Quer)(4) ...
- 动态规划-RMQ问题(ST算法)
文章目录 RMQ问题 ST算法 模板 例题 P2251 质量检测 P1816 忠诚 P2216 [HAOI2007]理想的正方形 RMQ问题 RMQ(Range Minimum/Maximum Que ...
- RMQ问题(线段树算法,ST算法优化)
RMQ (Range Minimum/Maximum Query)问题是指: 对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在[i,j]里的最小(大)值 ...
最新文章
- Java程序员春招三面蚂蚁金服,1200页文档笔记
- 基于文本挖掘的企业隐患排查质量分析模型
- python多列排序
- python的加减乘除运算_python实现四则运算
- 1月31日 资源网站(素材模板)
- arpg网页游戏之地图(二)
- 基于MySQL的调度系统_仓储调度系统的设计与实现(SSH,MySQL)
- 中科大图形学暑期课程资料
- win10系统同时设置静态IP和动态IP
- HTML之meta属性大全
- ecshop分类间隔变色_JS小案例:循环间隔重复变色
- 浏览器被360劫持怎么办
- OpenEuler树莓派基础实验 20212802范辰宇
- BOM 和 DOM 的区别是什么?
- 零知识证明 - 从QSP到QAP
- 使用TRA命令进行磁带备份的命令汇总(转)
- ctf解密图片得到flag_CTF从入门到进(fang)阶(qi)之MISC
- 宋氏极简美学的编码风格
- PCL点云的旋转平移矩阵
- 什么是Ajax 和 json
热门文章
- [SOLO ]SOLO: Segmenting Objects by Locations代码解读笔记(ECCV. 2020)
- 装linux时电脑蓝屏如何解决,笔记本电脑开机蓝屏 怎么用u盘安装ubuntu
- 图像光学失真预处理_摄影中的光学失真是什么?
- 马拉松和直播,张朝阳的“沉下去,浮上来”
- java毕业设计维保管理系统mybatis+源码+调试部署+系统+数据库+lw
- laravel 项目实现邮箱验证功能
- A4黑白打印多少钱一张
- android日记 设计说明,基于Android的掌上校园系统的设计与实现毕业论文.doc
- 计算道路曲线要素的小程序
- C++实现pi/π/圆周率的计算方法