RMQ问题 (一本通)

讲解:

https://blog.csdn.net/niushuai666/article/details/6624672  非常详细!!!
https://www.cnblogs.com/YSFAC/p/7189571.html

下面是第一个连接的内容:

RMQ问题:区间最大最小值问题。

是指这样一个问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大值。这两个问题是在实际应用中经常遇到的问题,下面介绍一下解决这两种问题的比较高效的算法。当然,该问题也可以用线段树(也叫区间树)解决,算法复杂度为:O(N)~O(logN)。

2.RMQ算法

对于该问题,最容易想到的解决方案是遍历,复杂度是O(n)。但当数据量非常大且查询很频繁时,该算法无法在有效的时间内查询出正解。

本节介绍了一种比较高效的在线算法(ST算法)解决这个问题。所谓在线算法,是指用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

例题(一本通):

1541:【例 1】数列区间最大值  典型例题

有四个点超时,怎么过呀TAT

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int a[maxn],f[maxn][20];
int n,m;
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		f[i][0]=a[i];   //初始化 
	}
	for(int j=1;(1<<j)<=n;j++){  //外层是j!!! 
		for(int i=1;i+(1<<j)-1<=n;i++){
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		}
	}
	while(m--){
		int x,y;
		scanf("%d %d",&x,&y);
		//找到两个覆盖这个闭区间的最小幂区间
		int k=int(log((double)(y-x+1))/log(2.0));
		printf("%d
",max(f[x][k],f[y-(1<<k)+1][k]));
	}
return 0;
}

  

1542:【例 2】最敏捷的机器人

区间最大最小值问题

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int n,k;
int minn[maxn][20],maxx[maxn][20];

int main(){
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++){
		 scanf("%d",&minn[i][0]);
		 maxx[i][0]=minn[i][0];
	}
	for(int j=1;(1<<j)<=n;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			maxx[i][j]=max(maxx[i][j-1],maxx[i+(1<<(j-1))][j-1]);
			minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
		}
	}
	for(int i=1;i<=n-k+1;i++){
		int j=i+k-1;
		int p=int(log(double(j-i+1))/log(2.0));
		printf("%d %d
",max(maxx[i][p],maxx[j-(1<<p)+1][p]),min(minn[i][p],minn[j-(1<<p)+1][p]));
	}
return 0;
}
 

  

1543:【例 3】与众不同

现在求得不是最大最小值,而是连续序列的最大长度(条件:不相同)

sol:首先需要维护以i作为结束点时完美序列的最大长度,那么记录一个Start[i]表示以i为结束点时最长的序列的出发点
再用记录一段区间内如[L,R]中的以Pos(L<=Pos<=R)结尾的最长值,可以用ST表随便搞一下,询问时找到第一个Start[x]>=ql的x,答案就是max((x-1)-ql+1,ST[x,qr])
分别是前半段(x-1)-ql+1,后半段中的最大值st[x,qr]

流程:
求出上个a[i]的位置——>求出以i为结尾的最长完美序列的最大长度
—>对于单调递增的i,以i为结尾的最长完美序列的开头也单调递增
l[i]=(last[i]>=l[i-1])?last[i]+1:l[i-1])
——>二分答案找出第一个以x为结尾的最长完美序列的开头大于L,设这个下标为loc
——>ans=max((loc-1)-L+1,以loc——R为结尾的最长完美序列的长度);
——>用st表维护即可
原文链接:https://blog.csdn.net/YYHS_WSF/article/details/82556833

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=2e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//现在求得不是最大最小值,而是连续序列的最大长度(条件:不相同)
int n,m;
/*
 sol:首先需要维护以i作为结束点时完美序列的最大长度,那么记录一个Start[i]表示以i为结束点时最长的序列的出发点,
 再用记录一段区间内如[L,R]中的以Pos(L<=Pos<=R)结尾的最长值,可以用ST表随便搞一下,询问时找到第一个Start[x]>=ql的x,答案就是max((x-1)-ql+1,ST[x,qr])
 分别是前半段(x-1)-ql+1,后半段中的最大值st[x,qr] 
 
流程:
求出上个a[i]的位置——>求出以i为结尾的最长完美序列的最大长度
—>对于单调递增的i,以i为结尾的最长完美序列的开头也单调递增
l[i]=(last[i]>=l[i-1])?last[i]+1:l[i-1])
 ——>二分答案找出第一个以x为结尾的最长完美序列的开头大于L,设这个下标为loc
——>ans=max((loc-1)-L+1,以loc——R为结尾的最长完美序列的长度);
——>用st表维护即可
原文链接:https://blog.csdn.net/YYHS_WSF/article/details/82556833
*/
const int maxm=1e6+10;
int a[maxn],pre[maxm],f[maxn][25],lg[maxn];
//分别记录的是原值、以当前点结尾的最长完美序列的开头,当前区间的最长长度
int gett(int l,int r){
	if(l>r) return 0;
	int k=lg[r-l+1];
	return max(f[l][k],f[r-(1<<k)+1][k]);
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		a[i]+=maxm;
	}
	lg[0]=-1;
	for(int i=1;i<=n;i++){
		f[i][0]=min(f[i-1][0]+1,i-pre[a[i]]);
		//两种情况,选最小,因为看上一个的情况 
		pre[a[i]]=i;
		lg[i]=lg[i>>1]+1;
	}
	//dp结构 
	for(int j=1;j<=lg[n];j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		}
	}
	int l,r,loc,mid,ans;
	while(m--){
		scanf("%d %d",&l,&r);
		//两种策略
		l++;r++;  //注意输入数据的范围 
		loc=r+1;
		while(l<=r){
			mid=(l+r)/2;
			if(mid-f[mid][0]+1<=l) l=mid+1;  //这个长度小于l 
			else loc=mid,r=mid-1;
		}
		ans=max(loc-l,gett(loc,r));
		printf("%d
",ans);
	}
return 0;
}


//哪里错啦啊啊啊啊啊啊啊啊
#include<cstdio>
#include<iostream>
using namespace std;
inline int read()
{
	char ch=getchar();
	int ret=0; bool f=1;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=0;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
		ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
	return f?ret:-ret; 
}
 
int n,m,l,r,mid,loc,L,R,ans;
const int N=2e5+5,NUM=1e6+5;
int a[N],pre[NUM<<1],f[N][25],lg[N];
 
inline int get(int l,int r)
{
	if(l>r) return 0;
	int k=lg[r-l+1];
	return max(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		a[i]=read()+NUM;
		
	lg[0]=-1;
	for(int i=1;i<=n;i++)
		f[i][0]=min(i-pre[a[i]],f[i-1][0]+1),
		pre[a[i]]=i,
		lg[i]=lg[i>>1]+1;	
		
	for(int j=1;j<=lg[n];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]);
			
	while(m--)
	{
		L=l=read()+1,R=r=read()+1; loc=r+1;
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(mid-f[mid][0]+1<=L) l=mid+1;
				 else loc=mid,r=mid-1;
		}
		ans=max(loc-L,get(loc,R));
		printf("%d
",ans);
	}
	return 0;
}  

  

1544:天才的记忆

模板题,求最大值

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=2e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int a[maxn],f[maxn][20];
int n,m;
//不知道怎么改了,还是会超时3个点 
int main(){
	scanf("%d ",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		f[i][0]=a[i];   //初始化 
	}
	for(int j=1;(1<<j)<=n;j++){  //外层是j!!! 
		for(int i=1;i+(1<<j)-1<=n;i++){
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		}
	}
	scanf("%d",&m);
	while(m--){
		int x,y;
		scanf("%d %d",&x,&y);
		//找到两个覆盖这个闭区间的最小幂区间
		int k=int(log((double)(y-x+1))/log(2.0));
		printf("%d
",max(f[x][k],f[y-(1<<k)+1][k]));
	}
return 0;
}

  

1545:Balanced Lineup

也是模板题,最大最小值之差

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=5e4+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//区间最大最小差值 
int n,m;
int mi[maxn][20],ma[maxn][20];
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&mi[i][0]);
		ma[i][0]=mi[i][0];
	}
	for(int j=1;(1<<j)<=n;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
			ma[i][j]=max(ma[i][j-1],ma[i+(1<<(j-1))][j-1]);
		}
	}
	int l,r,minans,maxans;
	while(m--){
		scanf("%d %d",&l,&r);
		int k=int(log(double(r-l+1))/log(2.0));
		minans=min(mi[l][k],mi[r-(1<<k)+1][k]);
		maxans=max(ma[l][k],ma[r-(1<<k)+1][k]);
		printf("%d
",maxans-minans);
	}
return 0;
}

  

1546:NOIP2011 选择客栈

 这道题是求方案数

有两种方法,第一种会超时4个点,就是枚举所有颜色相同的点,看能不能这样消费

第二种就是不枚举,看那些不合理,不合理的是连续的一段,因为这毕竟是个区间,所以,就直接减去这一段

首先一段区间的最小值用ST表预处理一下(n*logn),之后对于每种颜色做一遍统计,易知不合法的应该是一整段的,认为那段个数有Sum个,那么就去掉Sum*(Sum-1)/2种,(n*k)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=2e6+10;
const int M=1e4+2;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int mon[maxn];
//int color[M][210];
//改变为:
int pre[maxn]; //与i颜色相同的前一个客栈的位置; 
int h[maxn];  //最后一个颜色为x的客栈的位置; 
int f[maxn][20];
int n,k,p;
//有四个点超时了 
int main(){
	scanf("%d %d %d",&n,&k,&p);
	for(int i=1;i<=n;i++){
		int x,y;
		scanf("%d %d",&x,&y);
		f[i][0]=y;
		//color[x][++color[x][0]]=i;
		pre[i]=h[x];  //上一个 
		h[x]=i;  //结尾 
	}
	for(int j=1;(1<<j)<=n;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		}
	}
	LL ans=0;
	for(int x=0;x<k;x++){
		for(int i=h[x];i;i=pre[i])
		for(int j=pre[i];j;j=pre[j]){
			int xx=j,yy=i;
			int k=log2(yy-xx+1);
			if(min(f[xx][k],f[yy-(1<<k)+1][k])<=p) ans++;
		}
		/*
		if(color[i][0]>1){
			int num=color[i][0];
			//sort(color[i]+1,color[i]+1+num);  不需要排序 
			for(int j=1;j<=num;j++){
				for(int z=j+1;z<=num;z++){
					int l=color[i][j],r=color[i][z];
					//cout<<l<<" "<<r<<endl;
					int k=int(log(double(r-l+1))/log(2.0));
					if(min(f[l][k],f[r-(1<<k)+1][k])<=p) ans++;
				}
			}
		}
		*/
	}
	printf("%d
",ans);
return 0;
}



#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=2e6+10; 
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
/*
首先一段区间的最小值用ST表预处理一下(n*logn),之后对于每种颜色做一遍统计,易知不合法的应该是一整段的,认为那段个数有Sum个,那么就去掉Sum*(Sum-1)/2种,(n*k)
*/
int bin[23],lg[maxn];
int n,k,p;
int cor[maxn],f[maxn][23];
LL solve(int c){   //这个颜色客栈的方案数 
	LL ans=0;
	int summ=0;
	for(int i=1;i<=n;i++) if(cor[i]==c) summ++;
	ans=1LL*((summ)*(summ-1)/2);
	int pre=0;
	summ=0;
	for(int i=1;i<=n;i++){
		if(cor[i]==c){
			if(!pre) {
				pre=i;summ++;continue;
			}
			int oo=lg[i-pre+1];
			int minn=min(f[pre][oo],f[i-bin[oo]+1][oo]);
			if(minn>p) summ++;  //不可以的 
			else {
				ans-=1LL*((summ*(summ-1))/2);
				pre=i;
				summ=1;
			}
		}
	}
	ans-=1LL*(summ*(summ-1)/2);
	return ans;
}
int main(){
	scanf("%d %d %d",&n,&k,&p);
	bin[0]=1;
	for(int i=1;i<=19;i++) bin[i]=bin[i-1]<<1;
	lg[0]=-1;
	for(int i=1;i<maxn;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;i++){
		scanf("%d",&cor[i]);
		scanf("%d",&f[i][0]);
	}
	for(int j=1;j<=19;j++){
		for(int i=1;i+bin[j]-1<=n;i++){
			f[i][j]=min(f[i][j-1],f[i+bin[j-1]][j-1]);
		}
	}
	LL ans=0;
	for(int i=0;i<k;i++) ans+=solve(i);
	printf("%lld
",ans);
return 0;
}

  

原文地址:https://www.cnblogs.com/shirlybaby/p/12775009.html