0
点赞
收藏
分享

微信扫一扫

【单调队列&单调栈专题】&【蓝桥杯备考训练】:矩形牛棚、单调栈、滑动窗口、子矩阵、最大子序和、烽火传递【已更新完成】


输入样例:
3 4 2
1 3
2 1
输出样例:
6
思路:

对于每行(预处理好每个方块上面最多能用的方块的个数),枚举其中的每列,分别判断左右两边第一列比该行少的位置,然后底乘高算出面积,维护最大值即可

预处理的过程:
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(g[i][j]==0)
			{
				h[i][j]=h[i-1][j]+1;
			}
		}//记录这一位置上面能用的格子是多少 
判断左右边界的过程:
int work(int a[])
{
	a[0]=-1,a[m+1]=-1;
	int tt=0;
	//枚举左边第一个比这个位置上面能用的格子少的左边界 
	stk[++tt]=0;//把左边界加进去 
	for(int i=1;i<=m;i++)
	{
		while(a[i]<=a[stk[tt]])tt--;//栈顶元素大于等于当前元素的话弹出栈顶 
		l[i]=stk[tt];
		stk[++tt]=i; 
	} 
	
	tt=0;
	stk[++tt]=m+1;
	for(int i=m;i>=1;i--)
	{
		while(a[stk[tt]]>=a[i])tt--;
		
		r[i]=stk[tt];
		
		stk[++tt]=i;
	}
	int res=0;
	
	for(int i=1;i<=m;i++)
	{
		res=max(res,a[i]*(r[i]-l[i]-1));
	}
	return res;
}
代码:
#include<bits/stdc++.h>

using namespace std;

const int N=3010;

int n,m,p;

int g[N][N],h[N][N];

int l[N],r[N];

int stk[N];

int work(int a[])
{
	a[0]=-1,a[m+1]=-1;
	int tt=0;
	//枚举左边第一个比这个位置上面能用的格子少的左边界 
	stk[++tt]=0;//把左边界加进去 
	for(int i=1;i<=m;i++)
	{
		while(a[i]<=a[stk[tt]])tt--;//栈顶元素大于等于当前元素的话弹出栈顶 
		l[i]=stk[tt];
		stk[++tt]=i; 
	} 
	
	tt=0;
	stk[++tt]=m+1;
	for(int i=m;i>=1;i--)
	{
		while(a[stk[tt]]>=a[i])tt--;
		
		r[i]=stk[tt];
		
		stk[++tt]=i;
	}
	int res=0;
	
	for(int i=1;i<=m;i++)
	{
		res=max(res,a[i]*(r[i]-l[i]-1));
	}
	return res;
}

int main()
{
	cin>>n>>m>>p;
	
	for(int i=0;i<p;i++)
	{
		int r,c;
		cin>>r>>c;
		
		g[r][c]=1;//标记为破坏 
		
	}
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(g[i][j]==0)
			{
				h[i][j]=h[i-1][j]+1;
			}
		}//记录这一位置上面能用的格子是多少 
	
	int res=0;
	
	for(int i=1;i<=n;i++)
	{
		res=max(res,work(h[i]));
	}
	cout<<res;
	return 0;
} 
输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2
思路:

经典单调栈模板

基本步骤:
1、维护单调性
while(tt>0 && s[tt]>=x)tt--;
2、处理要求的操作
if(tt<=0)cout<<"-1"<<" ";
		else cout<<s[tt]<<" ";
3、入栈
s[++tt]=x;
代码:
#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 +5 ;

int tt;
int s[N];
 

int main()
{
	int n;
	cin>>n;
	while(n--)
	{
	    int x;
	    cin>>x;
		while(tt>0 && s[tt]>=x)tt--;
		if(tt<=0)cout<<"-1"<<" ";
		else cout<<s[tt]<<" ";
		s[++tt]=x;
	}	
	return 0;
}
输入样例:
8 3
1 3 -1 -3 5 3 6 7
输出样例:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
思路:

经典单调队列模板

基本步骤(以求最大值为例):
1、维护单调性(在尾部做处理)
while(hh<=tt && a[i]>dq[tt])tt--;
2、入队
dq[++tt]=a[i];
3、判断是否滑出窗口,滑出则hh++
if(i-k>=0 && dq[hh]==a[i-k])hh++;
4、做要求的处理
if(i+1>=k)cout<<dq[hh]<<" ";	
代码:
#include<bits/stdc++.h>

using namespace std;

const int N=1e6+5;

int n,k;

int tt=-1,hh;
int dq[N],a[N];

int main()
{
	cin>>n>>k;
	
	for(int i=0;i<n;i++)scanf("%d",&a[i]);
	
	//求窗口最小值 
	for(int i=0;i<n;i++)
	{
		while(hh<=tt && a[i]<dq[tt])tt--;

		dq[++tt]=a[i];
		
		//
		if(i-k>=0 && dq[hh]==a[i-k])hh++;
		
		//队头一定是最小的
		if(i+1>=k)cout<<dq[hh]<<" "; 
	}	
	cout<<endl;
	
	//求最大值
	tt=-1,hh=0;
	for(int i=0;i<n;i++)
	{
		while(hh<=tt && a[i]>dq[tt])tt--;
		
		dq[++tt]=a[i];
		
		if(i-k>=0 && dq[hh]==a[i-k])hh++;
		
		if(i+1>=k)cout<<dq[hh]<<" ";	
		
	} 
	return 0;
}
输入样例:
2 3 1 2
1 2 3
4 5 6
输出样例:
58
样例解释

1×2+2×3+4×5+5×6=581×2+2×3+4×5+5×6=58。

思路:

固定左右边界进行枚举,这时候从上上边界往下边界枚举,每次在每个区域中在每行的最小值中选出最小的,在每行的最大值中选出最大的,这个区域的最大值乘最小值就是该矩阵的价值

固定左右边界进行枚举的过程:
	
	for(int i=b-1;i<m;i++)//固定好列区间 
	{
		for(int j=0;j<n;j++)A[j]=maxr[j][i];//每行:把每个窗口最大的数取出来
		getmax(A,B,n,a);//存到B中 
		for(int j=0;j<n;j++)A[j]=minr[j][i];//每行:把每个窗口最小的数取出来 
		getmin(A,C,n,a);//存到C中 
		for(int j=a-1;j<n;j++)	
		{
			res=(res+(LL)B[j]*C[j])%MOD;
		}
	}
实现得到滑动窗口最大值最小值的函数:
void getmin(int a[],int b[],int total,int lenth)
{
	int tt=-1,hh=0;
	for(int i=0;i<total;i++)
	{
		//判断元素是否滑出窗口 
		if(hh<=tt && q[hh]<=i-lenth)hh++;
		//判断新元素和旧元素的大小关系确保队列单调
		while(hh<=tt && a[i]<=a[q[tt]])tt--; 	
		//入队 
		q[++tt]=i;
		//把最值交给存储数组 (队头一定是最小的) 
		if(i>=lenth-1)b[i]=a[q[hh]];//i>=k-1表示滑动窗口形成
		
	}
}


void getmax(int a[],int b[],int total,int lenth)
{
	int tt=-1,hh=0;
	for(int i=0;i<total;i++)
	{
		
		if(hh<=tt && q[hh]<=i-lenth)hh++;
		
		while(hh<=tt && a[i]>=a[q[tt]])tt--;
		
		q[++tt]=i;
		
		//队头一定是最大的,赋值给b 
		if(i>=lenth-1)b[i]=a[q[hh]];
	}
}
代码:
#include<bits/stdc++.h>

using namespace std;

const int N=1010;

int n,m,a,b; 

int g[N][N],minr[N][N],maxr[N][N]; 

int q[N];

typedef long long LL;

const int MOD=998244353;

void getmin(int a[],int b[],int total,int lenth)
{
	int tt=-1,hh=0;
	for(int i=0;i<total;i++)
	{
		//判断元素是否滑出窗口 
		if(hh<=tt && q[hh]<=i-lenth)hh++;
		//判断新元素和旧元素的大小关系确保队列单调
		while(hh<=tt && a[i]<=a[q[tt]])tt--; 	
		//入队 
		q[++tt]=i;
		//把最值交给存储数组 (队头一定是最小的) 
		if(i>=lenth-1)b[i]=a[q[hh]];//i>=k-1表示滑动窗口形成
		
	}
}


void getmax(int a[],int b[],int total,int lenth)
{
	int tt=-1,hh=0;
	for(int i=0;i<total;i++)
	{
		
		if(hh<=tt && q[hh]<=i-lenth)hh++;
		
		while(hh<=tt && a[i]>=a[q[tt]])tt--;
		
		q[++tt]=i;
		
		//队头一定是最大的,赋值给b 
		if(i>=lenth-1)b[i]=a[q[hh]];
	}
}

int main()
{
	cin>>n>>m>>a>>b;
	
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)scanf("%d",&g[i][j]);
	//for(int i=0;i<n;i++)
		//for(int j=0;j<m;j++)printf("%d",g[i][j]);
	//预处理出来每一行中滑动窗口的最值 
	for(int i=0;i<n;i++)
	{
		getmin(g[i],minr[i],m,b);
		getmax(g[i],maxr[i],m,b);
	}
	
	 
	int res=0;
	
	int A[N],B[N],C[N];
	
	for(int i=b-1;i<m;i++)//固定好列区间 
	{
		for(int j=0;j<n;j++)A[j]=maxr[j][i];//每行:把每个窗口最大的数取出来
		getmax(A,B,n,a);//存到B中 
		for(int j=0;j<n;j++)A[j]=minr[j][i];//每行:把每个窗口最小的数取出来 
		getmin(A,C,n,a);//存到C中 
		for(int j=a-1;j<n;j++)	
		{
			res=(res+(LL)B[j]*C[j])%MOD;
		}
	}
	cout<<res;
	return 0;
}
输入样例:
6 4
1 -3 5 1 -2 3
输出样例:
7
思路:

前缀和+滑动窗口

滑动窗口中维护前缀和的单调性的步骤:
//s[q[tt]] 即将被减去,当然是越小越好 
//如果s[i](前i个数的和)小于s[q[tt]](前面的前缀和)
//那么s[i]更适合被减去来求后面的前缀和 
while(hh<=tt && s[q[tt]]>=s[i])tt--;
代码:
#include<bits/stdc++.h>

using namespace std;

const int N=300000+10;

int n,m; 

long long  s[N];

int q[N],hh=-1,tt;

int main()
{
	cin>>n>>m;
	
	//前缀和 
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		s[i]+=s[i-1];
	}
	
	long long res=INT_MIN;
	for(int i=1;i<=n;i++)
	{
		//判断是否滑出窗口 
		if(hh<=tt && q[hh]<=i-m-1)hh++;
		//保持单调性 
		//while(hh<=tt &&  )
		
		res=max(res,s[i]-s[q[hh]]);
		
		//s[q[tt]] 即将被减去,当然是越小越好 
		//如果s[i](前i个数的和)小于s[q[tt]](前面的前缀和)
		//那么s[i]更适合被减去来求后面的前缀和 
		while(hh<=tt && s[q[tt]]>=s[i])tt--;
		
		
		q[++tt]=i;
				
	}
	cout<<res;
	return 0;
} 
输入样例:
5 3
1 2 5 6 2
输出样例:
4
思路:

动态规划+单调队列

f[i]表示第i个烽火台点燃需要的最小代价

维护单调性的步骤:
//维护单调性
while(hh<=tt && f[i]<=f[q[tt]])tt--;
代码:
#include<bits/stdc++.h>

using namespace std;

const int N=2e5+10;

int n,m; 

int a[N];

int q[N];

int hh,tt=-1;
//dp
int f[N];

int main()
{
	cin>>n>>m;
	
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}

	//前0个烽火台的最小代价是0 
	f[++tt]=0;
	int res=1e9;
	for(int i=1;i<=n;i++)
	{
		//f[i]表示第i个烽火台点燃需要的最小代价
		//超出i-m范围则不合法 
		if(q[hh]<i-m)hh++;
		f[i]=f[q[hh]]+a[i];
		//维护单调性
		
		while(hh<=tt && f[i]<=f[q[tt]])tt--;
		q[++tt]=i; 
	}

	for(int i=n-m+1;i<=n;i++)res=min(res,f[i]);
	cout<<res;
	return 0;
} 
/*
5 3
1 2 5 6 2

4


*/
举报

相关推荐

0 条评论