讲解:
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; }