问题翻译
对于每天的挤奶,农夫约翰的N头奶牛(1≤N≤50,000头)总是按照相同的顺序排着队。
一天,农夫约翰决定和一些奶牛组织一场极限飞盘游戏。
为了简单起见,他将从挤奶队伍中挑选连续的奶牛来玩游戏。
然而,对于所有的奶牛来说,它们的身高不应该相差太多。
Farmer John列出了Q(1≤Q≤20万)潜在牛群及其身高(1≤身高≤1,000,000)。
对于每一组,他希望你帮助确定组中最矮的奶牛和最高的奶牛之间的身高差异。
输入
第1行:两个用空格分隔的整数N和Q。
行2 . .N+1:第i+1行包含一个整数,它是奶牛i的高度
行N + 2 . .N+Q+1:两个整数A和B(1≤A≤B≤N),表示奶牛从A到B(含B)的范围。
输出
行1...Q:每一行包含一个整数,这是对一个答复的回应,表示该范围内最高和最短的牛之间的身高差。
人话翻译:
输入
第一行给出N和Q,N为牛的个数,Q为询问次数
接下来逐次给出N行牛的高度
接下来逐次给出Q行询问区间,左闭右闭
输出
输出Q行,每行为每次询问的对应答案
思考: 有N个数,M次询问,每次给定[L,R] ,求区间内的最大值。
当N<= 10, M<=10时?
当N<= 10^5,M<=10^5时呢?
要考虑到时间复杂度 O 哟!!!
这种问题是区间最值问题(RMQ)。
暴力的复杂度是O(N),对第一个情况是比较简单,但是第二种情况就会超时,可能要跑好久。
而线段数的复杂度是O(logN) 对于每次询问,但是M变成10^6时,O(MlogN)也可能会超时。
我们需要一个对于每个查询复杂度为O(1),这样整体的复杂度为O(M) ,也是今天的主角ST表。
倍增思想:从字面的上意思看就是成倍的增长 。最核心的就是利用 任意整数可以表示成若干个2 的 次幂项的和 “ 这一性质(13 = 2^3 + 2^2 +2^0 13=1101B )。
max操作允许区间重叠,也就是max(a,b,c)=max(max(a,b),max(b,c))
我们采用倍增思想,令f(i,j)为从a[i]开始的、连续2^j个数的最大值,显然:
f(i,0)=a[i](显然根据定义可得)f(i,j)=max(f(i,j-1),f(i+2^(j-1),j-1)
推导:
f(i,j) 为区间1 [ i, i+2^j -1] 的最大值
f(i,j-1) 为区间2 [ i,i+2^(j-1)-1 ] 的最大值
f(i+2^(j-1),j-1) 为区间3 [ i+2^(j-1),i+2^(j-1)+2^(j-1)-1 ] ,即[ i+2^(j-1),i+2^j-1] 的最大值
区间2 加 区间 3 正好把区间1给覆盖了。
[ l, r] 之间的长度为2的倍数时,例:找[1,4] 的最大值,我们直接给出f [1,2] 的值即可。但要是问[ 1,6] 的最大值呢?
这时我们就要用到max的性质了,我们可以求max( f[ 1, 2] ,f[ 3,2]) 即可。
为了解决每次查询,记询问区间长度为len,
我们从左端点向右找一段长为2^log(len)的区间(绿色部分),
右端点向左也找一段长为2^log(len)的区间(蓝色部分),
显然这两段区间已经覆盖了整个区间(中间重叠了一块紫色色部分),取最大值即可
当然为了保证询问复杂度为O(1) ,我们需要提前预处理出每个log(len)向下取整后的值。整个算法总时间复杂度为O(Nlog N+M) 。
第二个区间左端点X=R+1-2^len
即max(L,R)=max(f[L,len],f[R+1-2^len],len])
其中len=log(R-L+1)
求min把max换成min即可
//https://zhuanlan.zhihu.com/p/271981230
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1000001;
int a[N]= {};
int lg[N]= {-1};
int maxn[N][21]= {};// 第二维的大小根据数据范围决定,不小于log(MAXN),而logN= 19.931570012
int minn[N][21]={};
int main() {
int n=0,m=0;
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
lg[i]=lg[i/2]+1;
}
//我们采用倍增思想,令f(i,j)为从a[i]开始的、连续2^j个数的最大值,显然:
//f(i,0)=a[i](根据定义可得) f(i,j)=max(f(i,j-1),f(i+2^(j-1),j-1))
for(int i=1; i<=n; i++) {
maxn[i][0]=a[i];
minn[i][0]=a[i];
}
//maxn[j][i]=max(j,j+2^i-1) 由于j+2^i-1<=n,因此j<=n-2^i+1
for(int i=1; i<=lg[n]; i++) {//i<=lg[n]是因为i是指数项,要遍历[L,R],需要j=L时j+2^i-1<=R,则 2^i<=R-L+1,i<=log(R-L+1),本篇代码L=1,R=n,因此log(R-L+1)=log(n)
for(int j=1; j+(1<<i)-1<=n; j++) {//f(j,i)=max(f(j,i-1),f(j+2^(i-1),i-1)) j<=n-2^i
maxn[j][i]=max(maxn[j][i-1],maxn[j+(1<<(i-1))][i-1]);//1<<i-1=i+2^(i-1)
minn[j][i]=min(minn[j][i-1],minn[j+(1<<(i-1))][i-1]);
}
}
int l=0,r=0;
while(m--) {
scanf("%d%d",&l,&r);
int len=lg[r-l+1];//len=log(R-L+1)
// printf("%d\n",max(maxn[l][len],maxn[r-(1<<(len))+1][len]));//max(L,R)=max(f[L,len],f[R+1-2^len],len])
printf("%d\n",max(maxn[l][len],maxn[r-(1<<(len))+1][len])-min(minn[l][len],minn[r-(1<<(len))+1][len]));
}
return 0;
}
二刷:
左移右移知识点:
一: 对于int型变量,i=1,j=-1,i<<-1等价于j=255,i<<j,结果为-2147483648
详阅https://blog.csdn.net/weixin_30456039/article/details/101087025和https://www.cnblogs.com/zhangyongjian/p/3658569.html
二: +和 -优先级大于<<和>>,因此i+1<<j需要加括号 i+(1<<j)
三 i<<j=i+2^j-1,不要忘了-1!!
#include<cstdio> #include<algorithm> using namespace std; const int N = 1000001; int a[N]= {}; int lg[N]= {-1}; int maxn[N][21]= {};// 第二维的大小根据数据范围决定,不小于log(MAXN),而logN= 19.931570012 int minn[N][21]={}; int main(){ int n,Q; scanf("%d%d",&n,&Q); for(int i=1;i<=n;i++)//预处理对数 { scanf("%d",a+i); lg[i]=lg[i/2]+1; } //预处理区间动态规划 /* f(i,0)=a[i] 本着分解思想,把未知的f(i,j)分解为已知的f(i,j-1)和f(x,j-1),下面求解x f(i,j)=max(f(i,j-1),f(x,j-1)) f(i,j)=max[i,i+2^j-1] f(i,j-1)=max[i,2^(j-1)-1] f(x,j-1)=max[x,x+2^(j-1)-1] ∵x必须满足x+2^(j-1)-1=j 错误!!!这里混淆了f中j的数学意义 x+2^(j-1)-1=i+2^j-1 x=i+2^j-2^(j-1) x=i+2^(j-1)=i+1<<(j-1)-1 错误!! x=i+1<<(j-1)-1 错误!! 所以f(i,j)=max(f(i,j-1),f(i+1<<(j-1)-1,j-1)) 错误!! 错误点:1<<(j-1)=1+2^(j-1)-1 所以2^(j-1)=1<<(j-1) 因此x= i+(1<<(j-1)) 第二个错误点是<<的优先级没有+高,所以务必加上加号 */ for(int i=1;i<=n;i++) { maxn[i][0]=a[i]; minn[i][0]=a[i]; } for(int j=1;j<=lg[n];j++){//j的范围:i=1时 1+2^j-1<=n j<=logn j-1>=0 ∴1<=j<=lg[n] for(int i=1;i<=n-(1<<j)+1 ;i++){//i的范围:i>=1 i+2^j-1<=n i<=n+1-2^j 即i<=n-(1<<j)+1 所以1<=i<=n-(1<<j)+1 maxn[i][j]=max(maxn[i][j-1],maxn[i+(1<<(j-1))][j-1]); minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]); } } /* 查询:len=lg[R-L+1] max[L,R]=max(f(L,len),f(R-2^len+1,len)) X+2^len-1=R,X=R-2^len+1 2^len=(1<<len)-1 ∴max[L,R]=max(f(L,len),f(R-(1<<len),len) 错误!!R-(1<<len)+1才对 改正: X+2^len-1=R x=R-2^len+1 1<<len= 1+2^len-1 这是错因,没有-1 所以-2^len+1=1-(1<<len) 所以x=R+1-(1<<len) */ while(Q--) { int L,R,len; scanf("%d%d",&L,&R); len=lg[R-L+1]; printf("%d\n",max(maxn[L][len],maxn[R-(1<<len)+1][len])-min(minn[L][len],minn[R-(1<<len)+1][len])); } return 0; }