问题描述
给定 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[1−i]的和,若修改后数组的最大子数组为
(
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]+k∗x−(j−i−k)∗x)}=max{(S[j]−j∗x)−(S[i]−i∗x)}+2∗k∗x,j−i≥k(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]+(j−i)∗x)}=max{(S[j]+j∗x)−(S[i]+i∗x)},j−i<k(2)
分情况讨论
情况一:修改后数组的最大子数组的长度 l e n ≥ k len \geq k len≥k:
我的写法是:用 allSub
数组维护
S
[
i
]
−
i
∗
x
S[i] - i * x
S[i]−i∗x 的数值,同时开另一个数组,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,这里仅作代码实现