洛谷题单 算法2-2 常见优化技巧
洛谷题单 算法2-2 常见优化技巧
单调栈
单调栈最经典的应用,就是在一个数列里寻找距离元素最近的比其大/小的元素位置。
模板题,寻找每个元素右边第一个比它大的元素下标。
stack<int> s;
for(int i=n;i>=1;i--){
while(s.size()&&a[s.top()]<=a[i]) s.pop();
f[i]=s.size()?s.top():0;
s.push(i);
}
洛谷P1950 长方形
思路:
我们维护每个位置最多能向上延伸多少格。然后枚举每个点,计算将其当做底边,它向上延伸的最多格作为高度的长方形有多少个。那么我们只需要去计算左边和右边都各自能延伸到什么地方。那么我们就需要去 查找左边第一个小于该高度的位置 和 右边第一个小于该高度的位置 。显然用单调栈即可维护。但是这里要注意,我们维护左边时,应当查找 左边第一个小于等于该高度的位置, 否则我们就会出现重复计算。那么答案就是简单的乘法原理计算即可。
代码:
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
void Showball(){
int n,m;
cin>>n>>m;
vector<string> a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]="?"+a[i];
}
vector<vector<int>> h(n+1,vector<int>(m+1));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]=='*') h[i][j]=0;
else h[i][j]=h[i-1][j]+1;
}
}
auto calc=[&](int i){
i64 ret=0;
vector<int> l(m+1),r(m+1);
stack<int> stk;
for(int j=1;j<=m;j++){
while(stk.size()&&h[i][stk.top()]>h[i][j]) stk.pop();
l[j]=stk.size()?stk.top():0;
stk.push(j);
}
while(stk.size()){
stk.pop();
}
for(int j=m;j;j--){
while(stk.size()&&h[i][stk.top()]>=h[i][j]) stk.pop();
r[j]=stk.size()?stk.top():m+1;
stk.push(j);
}
for(int j=1;j<=m;j++){
ret+=(j-l[j])*(r[j]-j)*h[i][j];
}
return ret;
};
i64 ans=0;
for(int i=1;i<=n;i++){
ans+=calc(i);
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
单调队列
滑动窗口维护区间最值问题
for(int i=0;i<n;i++){
if(hh<=tt&&k<i-q[hh]+1) hh++;
while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
q[++tt]=i;
if(i+1>=k) printf("%d ",a[q[hh]]);
}
洛谷P2216 [HAOI2007] 理想的正方形 -- 二维单调队列
模板题:
给你一个矩阵,维护所有子矩阵的最值。
思路:
我们先维护每一行长度为 \(n\) 的所有子矩阵的最值 \(xmax\) 和 \(xmin\) 。然后只需要这两个数组再用单调队列维护
列的最值即可。
代码:
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
void Showball(){
int a,b,n;
cin>>a>>b>>n;
vector<vector<int>> g(a+1,vector<int>(b+1));
for(int i=1;i<=a;i++){
for(int j=1;j<=b;j++){
cin>>g[i][j];
}
}
vector<vector<int>> xmax(a+1,vector<int>(b+1));
auto xmin=xmax,maxn=xmax,minn=xmax;
vector<int> q(1010),qq(1010);
for(int i=1;i<=a;i++){
int h=0,t=-1;
int hh=0,tt=-1;
for(int j=1;j<=b;j++){
if(h<=t&&j-q[h]+1>n) h++;
while(h<=t&&g[i][q[t]]<=g[i][j]) t--;
q[++t]=j;
if(j>=n) xmax[i][j-n+1]=g[i][q[h]];
if(hh<=tt&&j-qq[hh]+1>n) hh++;
while(hh<=tt&&g[i][qq[tt]]>=g[i][j]) tt--;
qq[++tt]=j;
if(j>=n) xmin[i][j-n+1]=g[i][qq[hh]];
}
}
for(int i=1;i+n-1<=b;i++){
int h=0,t=-1;
int hh=0,tt=-1;
for(int j=1;j<=a;j++){
if(h<=t&&j-q[h]+1>n) h++;
while(h<=t&&xmax[q[t]][i]<=xmax[j][i]) t--;
q[++t]=j;
if(j>=n) maxn[j-n+1][i]=xmax[q[h]][i];
if(hh<=tt&&j-qq[hh]+1>n) hh++;
while(hh<=tt&&xmin[qq[tt]][i]>=xmin[j][i]) tt--;
qq[++tt]=j;
if(j>=n) minn[j-n+1][i]=xmin[qq[hh]][i];
}
}
int ans=1e9;
for(int i=1;i+n-1<=a;i++){
for(int j=1;j+n-1<=b;j++){
ans=min(ans,maxn[i][j]-minn[i][j]);
}
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
只需要维护最大值即可,只不过子矩阵变成了 \(r\times s\) 的。
数组初始化为 \(-1\),因为 \(t\) 可能为 \(0\) ,表示一开始这块像素就是坏的。
然后如果该子矩阵的最小值不为 \(-1\),那么用子矩阵最大值更新答案即可。
P1714 切蛋糕 单调队列优化/线段树
相当于在 最大子段和 问题上增加了一个长度限制。
考虑 \(f_i\) 为以 \(i\) 结尾的最长子段和。
\(f_i=max(S_i-S_j)\) ,其中 \(j \in [i-m,i-1]\)。
\(S_i\) 是定值,因此可以拿出来。
\(f_i=S_i-min(S_j)\) , 其中 \(j \in [i-m,i-1]\)。
所以我们直接求区间最小值即可。离线操作,所以线段树,ST表都可以解决。
但是代码相对不好写。发现区间长度固定,滑动窗口 模型,直接单调队列即可。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
void Showball(){
int n,m;
cin>>n>>m;
vector<i64> a(n+1),s(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]+a[i];
}
vector<int> q(2*n+1);
int hh=0,tt=-1;
i64 ans=a[1],minn=0;
for(int i=1;i<=n;i++){
ans=max(ans,s[i]-minn);
if(hh<=tt&&i-q[hh]+1>m) hh++;
while(hh<=tt&&s[q[tt]]>=s[i]) tt--;
q[++tt]=i;
if(i>=m) minn=s[q[hh]];
else minn=min(minn,s[i]);
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
最大子矩形问题
洛谷P1578 奶牛浴场
现将四个顶点加入障碍物中,然后按照横坐标排序,从左往右和从右往左扫描更新答案。
最后再按纵坐标排序,相邻两项之间构成一个矩形。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
//最大子矩形问题
void Showball(){
int l,w,n;
cin>>l>>w>>n;
vector<array<int,2>> a(n+4);
for(int i=0;i<n;i++) cin>>a[i][0]>>a[i][1];
//设置4个顶点为障碍点
a[n++]={0,0};a[n++]={0,w};a[n++]={l,0};a[n++]={l,w};
int L,R,U,D;
//从左往右搜
sort(a.begin(),a.end(),[&](array<int,2> x,array<int,2> y){
return x[0]==y[0]?x[1]<y[1]:x[0]<y[0];
});
int ans=0;
for(int i=0;i<n;i++){
L=a[i][0],D=0,U=w;
for(int j=i+1;j<n;j++){
R=a[j][0];
ans=max(ans,(R-L)*(U-D));
if(a[j][1]<a[i][1]) D=max(D,a[j][1]);
else U=min(U,a[j][1]);
}
}
//从左往右搜
for(int i=n-1;i>=0;i--){
L=a[i][0],D=0,U=w;
for(int j=i-1;j>=0;j--){
R=a[j][0];
ans=max(ans,(L-R)*(U-D));
if(a[j][1]<a[i][1]) D=max(D,a[j][1]);
else U=min(U,a[j][1]);
}
}
//处理特殊情况
sort(a.begin(),a.end(),[&](array<int,2> x,array<int,2> y){
return x[1]==y[1]?x[0]<y[0]:x[1]<y[1];
});
for(int i=0;i<n-1;i++){
ans=max(ans,l*(a[i+1][1]-a[i][1]));
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}