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