DP的优化途径---单调队列

1.前缀和+单调队列:https://www.acwing.com/problem/content/137/

我们先预处理下前缀和,以下标为i的点为有边界:

ans=max(ans,sum[i]-sum[j])(i-m\leqslant j\leqslant i-1)

也就是求sum[j](i-m\leqslant j\leqslant i-1)的min,考虑到j的范围是定值,用单调队列维护即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node{
    int t;
    ll zhi;
};
int n,m;
ll a[300006];
ll sum[300006];
node q[300006];
int tail=-1,head=0;
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
    ll ans=-1e18;
    for(int i=0;i<=n;i++){
        if(tail>=head&&(q[head].t<=i-m-1)) head++;
        if(tail>=head) ans=max(ans,sum[i]-q[head].zhi);
        while(tail>=head&&q[tail].zhi>=sum[i]) tail--;
        q[++tail]={i,sum[i]};
    }
    cout<<ans;
    
}

2.DP+单调队列:https://www.acwing.com/problem/content/1089/

先维护出来前缀和,令dp[i]表示前i个的最大效率,答案就是dp[n]

我们考虑状态转移:

dp[i]=max(dp[i-1],max(sum[i]-sum[j]+dp[j-1])(i-k\leqslant j\leqslant i-1))

于是我们把dp值放入单调队列即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

ll n,k;
ll a[100010],sum[100010],dp[100010];
ll q[100010],head=0,tail=-1;
int main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
    dp[0]=a[0];
    q[++tail]=0;
    for(int i=1;i<=n;i++){
        while(tail>=head&&i-k>q[head]) head++;
        dp[i]=max(sum[i]+dp[q[head]-1]-sum[q[head]],dp[i-1]);
        ll xx=dp[i-1]-sum[i];
        while(tail>=head&&xx>dp[q[tail]-1]-sum[q[tail]]) tail--;
        q[++tail]=i;
    }

   
    cout<<dp[n];

}

3.DP+单调队列:https://www.acwing.com/problem/content/1091/

令dp[i]表示前i个第i个一定选的最小花费:

dp[i]=a[i]+min(dp[j]),用单调队列维护。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[200010],dp[200010];
int q[200010],tail=-1,head=0;
int ans=0x7f7f7f7f;
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    q[++tail]=1;
    dp[1]=a[1];
    for(int i=2;i<=n;i++){
        while(tail>=head&&q[head]<i-m) head++;
        if(i<=m) dp[i]=a[i];
        else dp[i]=a[i]+dp[q[head]];
        while(tail>=head&&dp[i]<dp[q[tail]]) tail--;
        q[++tail]=i;
    }
    //for(int i=1;i<=n;i++) cout<<dp[i]<<" ";
    for(int i=n-m+1;i<=n;i++) ans=min(ans,dp[i]);
    cout<<ans;
}

4.二维的单调队列:https://www.acwing.com/problem/content/1093/

想到要维护出来每一个正方形的max与min,我们可以考虑先对行进行单调队列,求出行方向连续k个的最值,然后再对列进行一遍,这样就求出每一个正方行的最值了。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m, k, res = 1e9;
int w[N][N], minv[N][N], maxv[N][N], que[N];
int a[N], b[N], c[N], d[N];
void get_max(int a[], int f[], int m)
{
    int hh = 0, tt = -1;
    for (int i = 1; i <= m; i ++ )
    {
        while (hh <= tt && i - que[hh] >= k) hh ++ ;
        while (hh <= tt && a[i] >= a[que[tt]]) tt -- ;
        que[ ++ tt] = i;
        f[i] = a[que[hh]];
    }
}
void get_min(int a[], int f[], int m)
{
    int hh = 0, tt = -1;
    for (int i = 1; i <= m; i ++ )
    {
        while (hh <= tt && i - que[hh] >= k) hh ++ ;
        while (hh <= tt && a[i] <= a[que[tt]]) tt -- ;
        que[ ++ tt] = i;
        f[i] = a[que[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], minv[i], m);
        get_max(w[i], maxv[i], m);
    }

    for (int i = k; i <= m; i ++ )
    {
        for (int j = 1; j <= n; j ++ )
        {
            a[j] = maxv[j][i];
            b[j] = minv[j][i];
        }
        get_max(a, c, n);
        get_min(b, d, n);
        for (int j = k; j <= n; j ++ ) res = min(res, c[j] - d[j]);
    }
    printf("%d\n", res);
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值