最大子数组变式 - 贪心 + 单调队列

问题描述

给定 n 个数字,可以进行 k 次修改,修改一个元素可以使得这个元素的值增加 x,此后没有进行修改的位置的值会减少 x. 输出修改后数组可以得到的最大子数组之和

本题来源于Codeforces,原题链接 https://codeforces.com/problemset/problem/1796/D

贪心选择

S [ i ] S[i] S[i] 表示 a [ 1 − i ] a[1-i] a[1i]的和,若修改后数组的最大子数组为 ( i , j ] (i, j] (i,j],则可以根据上述贪心策略得到下面两种情况的贪心选择
a n s = m a x { ( S [ j ] − S [ i ] + k ∗ x − ( j − i − k ) ∗ x ) } = m a x { ( S [ j ] − j ∗ x ) − ( S [ i ] − i ∗ x ) } + 2 ∗ k ∗ x , j − i ≥ k ( 1 ) ans = max{\{(S[j]-S[i]+k*x-(j-i-k)*x)\}} \\=max{\{(S[j]-j*x)-(S[i]-i*x)\}}+2*k*x, j-i\geq k \quad (1) ans=max{(S[j]S[i]+kx(jik)x)}=max{(S[j]jx)(S[i]ix)}+2kx,jik(1)
a n s = m a x { ( S [ j ] − S [ i ] + ( j − i ) ∗ x ) } = m a x { ( S [ j ] + j ∗ x ) − ( S [ i ] + i ∗ x ) } , j − i < k ( 2 ) ans = max{\{(S[j]-S[i]+(j-i)*x)}\} \\=max\{(S[j]+j*x)-(S[i]+i*x)\},j-i<k \quad(2) ans=max{(S[j]S[i]+(ji)x)}=max{(S[j]+jx)(S[i]+ix)},ji<k(2)

分情况讨论

情况一:修改后数组的最大子数组的长度 l e n ≥ k len \geq k lenk

我的写法是:用 allSub数组维护 S [ i ] − i ∗ x S[i] - i * x S[i]ix 的数值,同时开另一个数组,minIdx[i]来存储 sub[1 ~ i] 的最小值所在的下标。

那么对于这种情况而言,只需要遍历一遍sub数组,利用下面的公式就可以算出这个情况的最大值。

ans = max(ans, allSub[i] - allSub[minIdx[i - k]] + 2 * k * x)

这边 i - k 的意义是在这个情况下子序列长度一定大于等于 k,因此要选择minIdx[i - k]

情况二:修改后数组的最大子数组的长度 l e n < k len < k len<k:

当 k = 0 时不应该有情况二,此时应该跳过这个过程

在这种情况下,修改后数组的最大子数组长度有一个上界 k,因此可以用单调队列来构建一个长度为 k的滑动窗口,始终保持这个窗口内的最小值为在队头。

长度为 k 的单调队列的模板应该是这样:

单调队列存储的是下标

for(int i = 1; i <= n; i++)
{
    if(!Q.empty() && i - Q.front() >= k) Q.pop_front();
    while(!Q.empty() && a[Q.back()] > a[i]) Q.pop_back();
    Q.push_back(i);
    ...
}

利用下面公式求解即可:

ans = max(ans, a[i] - a[Q.front()])

完整代码

#include<iostream>
#include<algorithm>
#include<deque>
using namespace std;
typedef long long ll;
const int N = 100010;
ll allSub[N];   // 存储 S[i] - i * x
ll allAdd[N];   // 存储 S[i] + i * x
ll minSub[N];   // minsub[i] 存储 allSub[1 ~ i] 中的最小值下标
ll n, k, x;
ll ans = -2e9;
int minIdx = 0;
ll minimum = 2e9;
int main()
{
    cin >> n >> k >> x;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
        
    for(int i = 1; i <= n; i++)
    {
        allSub[i] = allSub[i - 1] + a[i] - x;
        allAdd[i] = allAdd[i - 1] + a[i] + x;
    }
    
    for(int i = 0; i <= n; i++)
    {
        if(minimum > allSub[i])
        {
            minimum = allSub[i];
            minIdx = i;
        }
        minSub[i] = minIdx;
    }
    // 情况一
    for(int i = 1; i <= n; i++)
        if (i - k >= 0 && i - minSub[i - k] >= k)   // 处理长度大于等于 k 的情况
            ans = max(ans, allSub[i] - allSub[minSub[i - k]] + 2 * k * x);
    // 情况二
    deque<int> Q;
    if(k) // 窗口长度大于 0 时才有意义
    {
        for(int i = 1; i <= n; i++)
        {   // 若加入会超过滑动窗口的长度 则将队头元素移出
            if(!Q.empty() && i - Q.front() >= k) Q.pop_front(); 
            while(!Q.empty() && allAdd[Q.back()] > allAdd[i]) Q.pop_back();
            Q.push_back(i);
            ans = max(ans, allAdd[i] - allAdd[Q.front()]);
        }
    }
    cout << ans;
}

题解思路来源于问求 TA dxy,这里仅作代码实现

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值