单调队列优化DP问题acm

本文介绍了多个使用单调队列优化动态规划算法的C++代码实现,包括最大子序和、旅行问题、烽火传递、绿色通道、修剪草坪和理想的正方形等经典问题。通过这些实例,展示了如何利用单调队列提高动态规划解题效率,尤其在处理区间最值问题时的优势。
摘要由CSDN通过智能技术生成


#写在前面

基础数据结构里有滑动窗口

从正确的朴素dp再到优化

多重背包问题3就是一道单调队列优化的Dp问题

##最大子序和

https://www.acwing.com/problem/content/137/
在这里插入图片描述

----c++版

//滑动窗口+前缀和
#include<iostream>
#include<algorithm>
using namespace std;
#include<cstring>
const int N=300010, inf=1e9;
int n,m;
int s[N],q[N];


int main(){
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++){
        scanf("%d", &s[i]);
        s[i]+=s[i-1];
    }
    int hh=0, tt=0;
    int res=-inf;
    for(int i=1; i<=n;i++){
        if(q[hh]<i-m)hh++;
        res=max(res, s[i]-s[q[hh]]);
        while(hh<=tt&&s[q[tt]]>=s[i])tt--;
        q[++tt]=i;
    }
    
    printf("%d\n", res);
    
    return 0;
}

##旅行问题

https://www.acwing.com/problem/content/1090/

在这里插入图片描述

----c++版

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2e6+10;//两倍空间,破环成链

int n;
int o[N], d[N];
ll s[N];
bool ans[N];
int q[N*2];

int main(){
    scanf("%d", &n);
    for(int i=1; i<=n; i++)scanf("%d%d", &o[i], &d[i]);
    //顺时针做
    for(int i=1;i<=n;i++) s[i]=s[i+n]=o[i]-d[i];
    for(int i=1;i<=n*2;i++) s[i] += s[i-1];
    
    int hh=0, tt=-1;
    for(int i=n*2; i; i--){//从后往前  因为顺时针走时需要看到i后面长度为n的区间中s的最小值
        if(hh<=tt&&q[hh]>=i+n)hh++;
        while(hh<=tt&& s[q[tt]]>=s[i])tt--;
        q[++tt]=i;//先更新再求,因为要用到s[i]
        if(i<=n){
            if(s[q[hh]]>=s[i-1])ans[i]=true;
        }
        
    }
    
    //逆时针做
    d[0]=d[n];
    for(int i=1;i<=n; i++)s[i]=s[i+n]=o[i]-d[i-1];
    for(int i=1; i<=n*2;i++)s[i]+=s[i-1];//注意这里的写法
    
    hh=0,tt=-1;
    for(int i=1; i<=n*2; i++){//从前往后,因为逆时针走时需要看到i前面长度为n的区间中s的最小值
        if(hh<=tt && q[hh]<i-n)hh++;
        if(i>n){
            if(s[q[hh]]<=s[i])ans[i-n]=true;
        }
        while(hh<=tt&&s[q[tt]]<=s[i])tt--;
        q[++tt]=i;
    }
    for(int i=1;i<=n;i++)
        if(ans[i])puts("TAK");
        else puts("NIE");
    
    return 0;
}

##烽火传递

https://www.acwing.com/problem/content/1091/

由于必须要在区间中选一个,状态转移方程才能这样写,状态才能定义为 ”选第i个“

在这里插入图片描述

----c++版

#include<iostream>
#include<algorithm>
using namespace std;
const int N=200010;

int n,m;
int w[N];
int f[N];
int q[N];

int main(){
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++)scanf("%d", &w[i]);
    
    int hh=0, tt=0;//一开始有一个元素
    //f[0]=0;
    for(int i=1; i<=n; i++){
        if(q[hh]<i-m)hh++;
        f[i]=f[q[hh]]+w[i];//先求再更新,区间的维护需要用f[i]
        while(hh<=tt&&f[q[tt]]>=f[i])tt--;
        q[++tt]=i;
    }
    
    int res=1e9;
    for(int i=n-m+1; i<=n; i++)res=min(res, f[i]);
    
    printf("%d", res);
    
    return 0;
}

##绿色通道

https://www.acwing.com/problem/content/1092/

在这里插入图片描述

----c++版

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
//+了一层二分
const int N=50010;
int n,m;
int w[N];
int q[N], f[N];

bool check(int limit){
    int hh=0,tt=0;
    //f[0]=0;可省略
    for(int i=1;i<=n;i++){
        if(q[hh]<i-limit-1)hh++;  //注意这里其实扩宽了区间的长度,真正的区间是limit+1
        f[i] = f[q[hh]]+w[i];
        while(hh<=tt&& f[q[tt]]>=f[i])tt--;
        q[++tt]=i;
    }
    int res=1e9;
    for(int i=n-limit; i<=n; i++) res=min(res, f[i]);
        
    return res<=m;
}

int main(){
    cin>>n>>m;
    for(int i=1; i<=n; i++)cin>>w[i];
    
    int l=0,r=n;
    while(l<r){
        int mid=l+r>>1;//找最左的答案
        if(check(mid))r=mid;
        else l=mid+1;
    }
    
    printf("%d", r);
    return 0;
}

##修剪草坪

https://www.acwing.com/problem/content/1089/

在这里插入图片描述

----c++版

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100100;
typedef long long ll;

int n,m;
ll s[N];
ll f[N];
int q[N];

ll g(int i){
    return f[i-1]-s[i];
}

int main(){
    scanf("%d%d", &n,&m);
    for(int i=1; i<=n; i++){
        scanf("%lld", &s[i]);
        s[i]+=s[i-1];
    }
    
    int hh=0, tt=0;
    for(int i=1; i<=n; i++){
        if(q[hh]<i-m)hh++;
        f[i]=max(f[i-1], g(q[hh])+s[i]);
        while(hh<=tt&& g(q[tt])<=g(i))tt--;
        q[++tt]=i;
    }
    
    printf("%lld", f[n]);
    return 0;
}

##理想的正方形

https://www.acwing.com/problem/content/1093/
在这里插入图片描述

----c++版

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1010;

int n,m,k;
int w[N][N];
int row_max[N][N], row_min[N][N];

void get_min(int a[], int b[], int tot){
    int hh = 0, tt = -1, q[N];
    for(int i=1; i<=tot; i++){
        if(hh<=tt&& q[hh]<=i-k)hh++;
        while(hh<=tt&&a[q[tt]]>=a[i])tt--;
        q[++tt]=i;
        b[i]=a[q[hh]];
    }
}

void get_max(int a[], int b[], int tot){
    int hh = 0, tt = -1, q[N];
    for(int i=1; i<=tot; i++){
        if(hh<=tt&& q[hh]<=i-k)hh++;
        while(hh<=tt&&a[q[tt]]<=a[i])tt--;
        q[++tt]=i;
        b[i]=a[q[hh]];
    }
}

int main(){
    scanf("%d%d%d", &n, &m, &k);
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
            scanf("%d", &w[i][j]);
            
    for(int i=1; i<=n; i++){
        get_min(w[i], row_min[i], m);
        get_max(w[i], row_max[i], m);
    }
    
    int res=1e9;
    int a[N], b[N], c[N];
    for(int i=k; i<=m; i++){//对于第i列
        for(int j=1; j<=n; j++)a[j] = row_min[j][i];
        get_min(a, b, n);
        
        for(int j=1; j<=n; j++)a[j] = row_max[j][i];
        get_max(a, c, n);
        
        for(int j=k; j<=n; j++)res = min(res, c[j]-b[j]);
        
    }
    
    printf("%d", res);
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值