输入样例:
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
*/