zoukankan      html  css  js  c++  java
  • 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;
    }
    

      

  • 相关阅读:
    实用JS代码
    javascript-table出现滚动条表格自动对齐
    Javascript-关于for in和forEach
    Javascript-关于break、continue、return语句
    如何编写可怕的Java代码?
    请停止编写这么多的for循环!
    如何优雅地在Stack Overflow提问?
    为什么阿里巴巴Java开发手册中强制要求不要在foreach循环里进行元素的remove和add操作?
    Java异常处理只有Try-Catch吗?
    看完这篇还不会用Git,那我就哭了!
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12775009.html
Copyright © 2011-2022 走看看