POJ 3264 Balanced Lineup ST表(区间最值问题RMQ)

问题翻译

对于每天的挤奶,农夫约翰的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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值