倍增
跳跳蛙的故事
很久很久以前有一只跳跳蛙,他要从一条长为 \(n\) 笔直的路上的A地跳到B地。
能吃苦的跳跳蛙从A地开始,每次跳一步,终于在很久以后,\(d\) 天之后来到了B地。
又过了几年,跳跳蛙又想去B地。
可是跳跳蛙这次要参加一个紧急会议,一步一步跳未免太慢了,他会被炒鱿鱼的。
跳跳蛙决定第一步跳 \(2^k\) 单位,使得 \(k\) 是最大的满足跳 \(2^k\) 不超过B地的数。
如果他还没有到B地,那么它就再跳 \(2^l\) 单位,使得 \(l\) 是最大的满足跳 \(2^l\) 还不超过B地的数。
如此下去……经过了大约 \(\log _2 d\) 天之后来到了会议室,得到了领导的表扬。
后来,跳跳蛙知道了,原来这种策略就是倍增。
倍增算法
对于每一个点 \(i(1\le i\le n\),跳跳蛙只需要记录 \(c_{i,j}\) 表示从 \(i\) 这个点跳 \(2^j\) 步可以到达的位置。接着他就转移阵地:\(i=c_{i,j}\);于此做循环,直到到达终点。
比如,我们记录 \(c_{1,1}\) 了之后,发现 \(c_{1,2}\) 其实就是 \(c_{2,1}\),所以我们只要想到了怎么转移,\(c\) 数组是很好建立的。需要留心的是,如果 \(c_{i,j}\) 它 \(i+2^j>n\) 那么我们可以直接跳出这层循环因为当 \(j\) 增大时,以后的 \(j\) 永远是 \(i+2^j>n\) 的了。
ST表
建立数组 \(d\),用 \(d_{i,j}\) 表示从 \(i\) 这个位置开始的 \(2^j\) 个数当中的最大值是多少。注意到 \(2^0=1\),我们初始化 \(d_{i,0}=0\)。
怎么转移呢?对于 \(d_{i,j}\),他其实是等于 \(\max(d_{i+2^{j-1},j-1},d_{i,j-1})\) 的。
那么对于 \([l,r]\),如何 \(O(1)\) 地求出答案呢?我们考虑,从 \(l\) 开始的 \(2^k\) 个数中的最大值使得 \(k\) 是最大的满足 \(l+2^k-1\le r\) 的数,以及从 \(r\) 开始的往左的 \(2^k\) 个数中的最大值使得 \(k\) 是最大的满足 \(r-2^k+1\ge l\) 的数,取这两个最大值中的较大值,即为答案,因为这两个段一定包含了 \([l,r]\) 中的所有数,由是我们 \(O(1)\) 求得了答案。
代码:
#include <bits/stdc++.h> //这是一道卡常题
using namespace std;
int n,m,l,r,a[100005],d[100005][25],LOG[100005];
inline int read()
{
int x=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
inline void print(int x){
if(x/10) print(x/10);
putchar(x%10+48);
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++) d[i][0]=a[i]=read(),LOG[i]=log2(i);
for(int i=2,cnt=1;i<=n;i<<=1,cnt++)
for(int j=1;j+i-1<=n;j++)
d[j][cnt]=max(d[j][cnt-1],d[j+i/2][cnt-1]);
while(m--){
l=read(),r=read();
int t2=LOG[r-l+1],t1=r-(1<<t2)+1;
int x=d[l][t2],y=d[t1][t2];
print(max(x,y));
puts("");
}
return 0;
}
犹待更新,敬请期待...