题意#
给定数列 ,可以对其进行下列操作。
- 花费 魔法值,选择 中的一个区间 ,将 全部 .
- 花费 魔法值,选择 中的一个区间 ,将 全部 .
现在可以做若干次操作,使得序列 存在一个子区间,其元素之和不小于 .
求需要花费的最小魔法值。
,,,.
题解#
对于 操作,对整个序列都使用,一定不劣于只对序列的一段区间使用。
对于正数,直接 一定优于 .
对于负数,需要先做若干次 到正数,之后再 ,这样最优。
因此最终的操作序列一定先加后乘,形如:
由于 ,若通过乘法操作能使区间和变大,则最多进行 次乘法操作。
,所以可以枚举乘法操作的次数。
对于加法操作的次数,可以二分解决。
二分,每次 时,先线性求出执行完操作之后的新序列 ,之后递推地求出最大子段和。
求最大子段和,只需设 表示以 结尾的最大子段和,, 的最大值即为序列的最大子段和。
代码#
#include <bits/stdc++.h>
#define int __int128
using namespace std;
inline int read() {}
inline void out(int x) {}
const int MAXN = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f;
int n, va, vb, s, a[MAXN], b[MAXN], ans = inf;
bool check(int add, int Pow) {
for(int i = 1; i <= n; i++) b[i] = (a[i] + add) * Pow;
int curf = 0, maxf = 0;
for(int i = 1; i <= n; i++) {
curf = max(curf + b[i], b[i]);
maxf = max(maxf, curf);
}
return maxf >= s;
}
signed main() {
n = read(), va = read(), vb = read(), s = read();
for(int i = 1; i <= n; i++) a[i] = read();
//外层枚举乘法次数
for(int x = 0, Pow = 1; x <= 50; x++, Pow <<= 1ll) {
int l = 0, r = 1e15, mid, mmin = inf;
//内层二分加法次数
while(l <= r) {
mid = (l + r) >> 1ll;
if(check(mid, Pow)) r = mid - 1, mmin = mid;
else l = mid + 1;
}
if(mmin != inf)
ans = min(ans, vb * x + va * mmin);
}
out(ans); putchar('\n');
return 0;
}
cpp