讲解:

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)时间内回答每个查询。

注意:ST算法是只能用于求一些区间最值或者最大公约数,不能维护动态的数据

 

例题(一本通):

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

一开始做,超时了,是因为math库里面的log函数比较慢,需要预处理  logg[i]=logg[i>>1]+1;

#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=1e6+10;
int a[maxn],f[maxn][25];
int n,m;
int logg[maxn] ;
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++) {
    	scanf("%d",&a[i]);
	}
	logg[0]=-1;
	for(int i=1;i<=n;i++){
		logg[i]=logg[i>>1]+1;
		f[i][0]=a[i];
	}
    for(int j=1;j<=25;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=logg[(y-x+1)];
		printf("%d\n",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\n",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为结束点时最长的序列的出发点
st[i]=max(st[i-1],last[a[i]]+1)   

f[i]表示i为结尾的最长值  f[i]=i-st[i]+1   st[]和f[]都能够线性求解

那么对于询问的区间[l,r],需要注意的是需要分区域求解,如果在区间前部分有一些数据的st[]是小于l的,那么最长是m-l,如果后部分是大于l的,那就在后部分找最大值,其中m的二分找到的分界点,左边的st小于l,右边的大于l

而在求后部分求最大值的时候,就可以用ST算法了,求区间最值,mx[i][0]=f[i]

原文链接: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 maxm=1e6+5,LOGN=18;
int last[maxm<<1],mx[maxn][LOGN],f[maxn],st[maxn],logg[maxn];
//注意数组区别,last[]表示的是x上一次出现的位置,mx[][]表示的是区间长度最大值,f[]表示的是以i为结尾的完美序列长度,st[]表示的是以i为结尾的完美序列的起始位置 
int n,m,li,ri,x;
int findd(int li,int ri){ //二分找出区间中间点,左边的st在li左边,右边的st在li右边,要在两个区间内找到最大的长度值 
	if(st[li]==li) return li;  //左边区间最大值为0
	if(st[ri]<li) return ri+1; //没有左边区间
	int l=li,r=ri;
	while(l<=r){
		int mi=l+r>>1;
		if(st[mi]<li) l=mi+1;
		else r=mi-1;
	} 
	return l;
}
int que(int li,int ri){
	int x=logg[ri-li+1];
	return max(mx[li][x],mx[ri-(1<<x)+1][x]);
}
int main(){
    scanf("%d %d",&n,&m);
    logg[0]=-1;
    for(int i=1;i<=n;i++){
    	int x;
    	scanf("%d",&x);
    	st[i]=max(st[i-1],last[x+maxm]+1);  //因为输入的数可能为负
		f[i]=i-st[i]+1;
		logg[i]=logg[i>>1]+1;
		last[x+maxm]=i; 
	}
	for(int i=1;i<=n;i++) mx[i][0]=f[i]; //mx存的是区间长度最大值 
	for(int j=1;j<=LOGN;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			mx[i][j]=max(mx[i][j-1],mx[i+(1<<j-1)][j-1]);
		}
	}
	while(m--){
		scanf("%d %d",&li,&ri);
		li++;ri++;
		int mid=findd(li,ri);
		int ans=0,tmp;
		if(mid>li) ans=mid-li; //存在区间左边
		if(mid<=ri){  //如果存在区间右边 
  			tmp=que(mid,ri);
  			ans=max(ans,tmp);
		} 
		printf("%d\n",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\n",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\n",maxans-minans);
	}
return 0;
}

  

1546:NOIP2011 选择客栈

 感觉这道题可以直接模拟做,不用写这么长的代码

https://www.cnblogs.com/zxqxwnngztxx/p/7763997.html

这个帖子的两种方法都是很不错的,也比较好理解

法1:纯模拟

#include <iostream>
#include<ctime>
#include <algorithm>
using namespace std;
int n,k,p,ans;
int pre[110],last[110],ok[110];
//pre[x] i点前颜色为x的客栈数
//ok[x]     i点前满足能有价格<=p的客栈的颜色为x的客栈数
//last[x]   i点 前一个颜色 为x的点的编号

int main(){
	scanf("%d %d %d",&n,&k,&p);
	for(int i=1,tmp,x,y;i<=n;i++){
		scanf("%d %d",&x,&y);
		if(y<=p) tmp=i;  //记录下来当前色调的可行的最右客栈编号 
		if(tmp>=last[x]) ok[x]=pre[x];
		ans+=ok[x];
		pre[x]++;
		last[x]=i;
	}
	printf("%d",ans);
  return 0;
}

法2:vector存储每一种酒店

枚举每一种酒店里面的每一个酒店,过滤掉不合理的选择

#include<vector>
#include <iostream>
#include<ctime>
#include <algorithm>
using namespace std;
const int maxn=200001;
const int K=101;
int n,k,p,ans;
int w[maxn],ok[maxn];
vector<int> v[K];
void getnex(){
	ok[n+1]=n+1;
	for(int i=n;i>=1;i--){
		if(w[i]<=p) ok[i]=i; //就是这点能不能作为合理的
		else ok[i]=ok[i+1]; 
	}
}

int main(){
	scanf("%d %d %d",&n,&k,&p);
	for(int i=1,x;i<=n;i++){
		scanf("%d %d",&x,&w[i]);
		v[x].push_back(i);
	}
	getnex();
	for(int i=0,d,s;i<k;i++){
		d=0;
		s=v[i].size();
		for(int j=0;j<s;j++){
			d=max(d,j+1);
			while(ok[v[i][j]]>v[i][d]&&d<s) d++; //过滤掉不合理的选择 
			ans+=s-d;
		}
	}
	printf("%d\n",ans); 
  return 0;
}

  

 

  

 

 posted on 2020-04-25 20:09  shirlybabyyy  阅读(338)  评论(0编辑  收藏  举报