• 单调队列


    单调队列

    定义和性质

    一个队列内部的元素具有单调性的一种数据结构,分为单调递增队列和单调递减队列。
    单调递减队列队首元素为区间最大值,队尾元素为最近的大于新元素的元素下标。
    单调递增序列维护区间最小值和最近的小于新元素的元素下标。

    模板

    int l=1,r=0;
    for(int i=1;i<=n;i++){//单调不严格增
    	while(r>=l&&a[q[r]]>a[i]) r--;
    	q[++r]=i;
    }
    

    常见用法

    给定一个n个数的数列,从左至右输出每个长度为m的区间内的最小数。

    int l=1,r=0;
    for(int i=1;i<=n;i++){
        while(r>=l&&a[q[r]]>=a[i]) r--;
        q[++r]=i;
        while(r>=l && q[l]<i-m+1)l++;
        if(i>=m)printf("%d ",a[q[l]]);
    }
    

    给定一个长度为n的数列a,找到最长的区间,max-min <= k

    int l1=1,r1=0,l2=1,r2=0,now=1;
    for(int i=1;i<=n;i++){
    	while(r1>=l1&&a[q1[r1]]<a[i]) r1--;//单调不严格减
    	while(r2>=l2&&a[q2[r2]]>a[i]) r2--;//单调不严格增
    	q1[++r1]=i;
    	q2[++r2]=i;
    	while(r1>=l1&&r2>=l2&&a[q1[l1]]-a[q2[l2]]>k){
    		if(q1[l1]<q2[l2]) now=q1[l1++]+1;//先移动坐标小的 
    		else  now=q2[l2++]+1;//+1是因为队列最左边的元素在区间中,是不满足条件的
    	}
    	ans=max(ans,i-now+1);
    } 
    

    例题

    Feel Good

    题目链接

    给定区间\((1,n)\),定义\(f(a,b)\)为区间和*区间最小值,求\(max(f(a,b)),1\le a \le n,1 \le b\le n\)

    解法:
    将问题转化为,求每个元素作为最小值时的最大区间,计算出每个区间的贡献,对答案求max。一个元素的最大区间就是(左边最近的小于该元素的位置+1,右边最近小于该元素的位置-1)。

    //用stl_stack的写法,常数大不推荐
    #include <iostream>
    #include <cstdio>
    #include <stack>
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+5;
    ll a[maxn],sum[maxn],p1[maxn],p2[maxn];
    int main(){
    	int n;
    	cin>>n;
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    		sum[i]=a[i]+sum[i-1];
    	}
    	stack<ll>s;
    	for(int i=1;i<=n;i++){
    		while(!s.empty()&&a[s.top()]>=a[i]) s.pop();//单调增的队列
    		if(s.empty()) p1[i]=1;
    		else p1[i]=s.top()+1;//最近的小于该元素的位置+1
    		s.push(i); 
    	}
    	while(!s.empty()) s.pop();
    	for(int i=n;i>=1;i--){
    		while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
    		if(s.empty()) p2[i]=n;
    		else p2[i]=s.top()-1;//最近的小于该元素的位置-1
    		s.push(i); 
    	}
    	ll ans=-1,tx=0,ty=0;
    	for(int i=1;i<=n;i++){
    		ll temp=(sum[p2[i]]-sum[p1[i]-1])*a[i];
    		if(temp>ans){
    			ans=temp;
    			tx=p1[i];ty=p2[i];
    		}
    	}
    	printf("%lld\n%lld %lld",ans,tx,ty);
    }
    

    Ascending Rating

    题目链接

    给定区间(1,n),定义f(i,j)为区间(i,j)上的最大值,g(i,j)为从左往右遍历该区间上最大值的更新次数,求\(\Sigma f(i,i+m)和\Sigma g(i,i+m),(1\le i,i+m \le n)\)

    解法:
    最大值同上可以用坐标递增值递减的单调队列的队首元素表示,而更新次数可以用坐标递减值递减的单调队列的大小来表示,发现最大值也可以用后者的来维护,因此只需要从右往左建立递减的单调队列即可

    #include <iostream>
    #include <cstdio>
    using namespace std;
    typedef long long ll;
    const int maxn=1e7+5;
    ll a[maxn],q1[maxn],q2[maxn];
    ll ans1[maxn],ans2[maxn];
    int main(){
    	ll T,n,m,k,p,q,r,mod;
    	scanf("%lld",&T);
    	while(T--){
    		scanf("%lld%lld%lld%lld%lld%lld%lld",&n,&m,&k,&p,&q,&r,&mod);
    		for(int i=1;i<=k;i++)
    			scanf("%lld",&a[i]);
    		for(int i=k+1;i<=n;i++)
    			a[i]=(p*a[i-1]+q*i+r)%mod;
    		ll res1=0,res2=0;
    		ll l=n+1,r=n;
    		for(int i=n;i>=1;i--){
    			while(r>=l&&a[q2[l]]<=a[i]) l++;
    			q2[--l]=i;
    			while(r>=l&&q2[r]>i+m-1) r--;
    			if(i<=n-m+1){
    				res2+=(r-l+1)^i;
    				res1+=a[q2[r]]^i;
    			}
    		}
    		printf("%lld %lld\n",res1,res2);
    	}
    }
    

    OpenStreetMap

    题目链接

    给定矩阵\(M[n][m]\) ,a,b,对于M中每个a*b的子矩阵,求出子矩阵中的最小元素并求和

    解法:
    遍历每一行,从左至右存储每个长度为b的区间内的最小数,得到新矩阵\(B[n][m-b+1]\),遍历B的每一列,同样用单调递增队列就可以维护得到每个子矩阵的最小值。

    #include <iostream>
    using namespace std;
    typedef long long ll;
    const int maxn=3e3+5;
    int h[maxn][maxn],g[maxn*maxn],q[maxn];
    int a1[maxn][maxn];
    int main(){
    	int n,m,a,b,x,y,z;
    	scanf("%d%d%d%d",&n,&m,&a,&b);
    	scanf("%d%d%d%d",&g[0],&x,&y,&z);
    	for(int i=1;i<=n*m-1;i++)
    		g[i]=((ll)g[i-1]*(ll)x%z+(ll)y)%z;
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			h[i][j]=g[(i-1)*m+j-1];
    	for(int i=1;i<=n;i++){//算出每个横区间的最小值矩阵 
    		int l=1,r=0;
    		for(int j=1;j<=m;j++){
    			while(r>=l&&h[i][q[r]]>h[i][j]) r--;//不严格增(最小值)
    			q[++r]=j;
    			while(r>=l&&j-q[l]+1>b)l++;
    			if(j>=b) a1[i][j]=h[i][q[l]];
    		}
    	}
    	ll ans=0;
    	for(int j=b;j<=m;j++){
    		int l=1,r=0;
    		for(int i=1;i<=n;i++){
    			while(r>=l&&a1[q[r]][j]>a1[i][j]) r--;
    			q[++r]=i;
    			while(r>=l&&i-q[l]+1>a)l++;
    			if(i>=a)
    				ans+=a1[q[l]][j];
    		}
    	}
    	printf("%lld\n",ans);
    }
    

    Planting Trees

    题目链接

    给定一个n*n的矩阵和每个位置上的权值 ai,j ,求最大的子矩阵,满足子矩阵中最大值和最小值之差不超过m。

    解法:
    枚举子矩阵的上边与下边O(N^2),存储区间内n条宽度为1的矩阵的最小值和最大值,在下界递增的时候单条宽度1的矩阵最小值与最大值的维护是O(1)的,每条矩阵抽象成了一个点(有两个值),此时可以用单调队列在O(N)内求出最大值最小值相差不超过m的最大区间。

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    const int maxn=505;
    int a[maxn][maxn];
    int maxx[maxn],minn[maxn];
    int q1[maxn],q2[maxn];
    int main(){
    	int T;
    	scanf("%d",&T);
    	while(T--){
    		int n,m;
    		scanf("%d%d",&n,&m);
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				scanf("%d",&a[i][j]);
    		int ans=0;
    		for(int t=1;t<=n;t++){//枚举上界
    			for(int i=1;i<=n;i++)//初始化最大最小值 
    				minn[i]=maxx[i]=a[t][i];
    			for(int b=t;b<=n;b++){//枚举下界
    				if(b!=t){
    					for(int i=1;i<=n;i++){
    						minn[i]=min(minn[i],a[b][i]);
    						maxx[i]=max(maxx[i],a[b][i]);
    					}
    				}			
    				int l1=1,r1=0,l2=1,r2=0,now=1;
    				for(int i=1;i<=n;i++){
    					while(r1>=l1 && maxx[q1[r1]]<maxx[i]) r1--;//不严格递减(最大值) 
    					while(r2>=l2 && minn[q2[r2]]>minn[i]) r2--;//不严格递增(最小值)
    					q1[++r1]=i;
    					q2[++r2]=i;
    					while(r1>=l1&&r2>=l2&&maxx[q1[l1]]-minn[q2[l2]]>m){
    						if(q1[l1]<q2[l2])now=q1[l1++]+1;
    						else now=q2[l2++]+1;
    					}
    					ans=max(ans,(i-now+1)*(b-t+1));	
    				}
    			}
    		}
    		printf("%d\n",ans);
    	}
    }
    
  • 相关阅读:
    316. 去除重复字母
    331. 验证二叉树的前序序列化
    225. 用队列实现栈
    197. 上升的温度
    178. 分数排名
    177. 第N高的薪水
    小程序导航
    css3、js动画特效
    背景透明css
    h5新标签IE8不兼容怎么办?
  • 原文地址:https://www.cnblogs.com/ucprer/p/11254956.html
Copyright © 2020-2023  润新知