洛谷题单 算法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;
}
posted @ 2024-11-13 15:18  Showball  阅读(11)  评论(0编辑  收藏  举报