要点
- 不难想到gcd一下然后枚举每个开头走一圈,并记录一下数值。
- 最终答案是分情况的:1.能走几圈走几圈然后加上最后剩余的最大子段和;2.也可能是最后一圈后面的拖后腿了,所以最后一圈没走完就停,即长度为一圈长的最大子段和;3.一圈为负数时只考虑一圈内的即可,多走了反而变差。
- 求环形的、不超过某长度的最大子段和,用双端队列处理一下前缀和。
const int maxn = 1e4 + 5;
int T, n, m, k;
ll s, a[maxn];
ll calc(vector<ll> v, int length) {
if (!length) return 0LL;
ll ret = -INF;
vector<ll> sum;
deque<int> Q;//单增的
for (int i = 0; i < length; i++) v.push_back(v[i]);
sum.push_back(v[0]);
for (int i = 1; i < v.size(); i++) sum.push_back(sum.back() + v[i]);
for (int i = 0; i < v.size(); i++) {
while (Q.size() && Q.front() + length < i) Q.pop_front();//保证不超过长度
ll tmp = sum[i] - (Q.size() ? sum[Q.front()] : 0);//sum[I] - min{sum[j]}
ret = max(ret, tmp);
while (Q.size() && sum[Q.back()] >= sum[i]) Q.pop_back();//又靠前又大,不用活了
Q.push_back(i);
}
return ret;
}
ll solve() {
function<int(int, int)> gcd = [&](int a, int b) { return b ? gcd(b, a % b) : a; };
int t = gcd(k, n), rest = m % (n / t);
ll res = 0;
rep(d, 0, t - 1) {
vector<ll> v;
ll sum = 0LL;
for (int i = d, j = 1; j <= n / t; j++) {//走完一圈
v.push_back(a[i]);
sum += a[i];
i = (i + k) % n;
if (i == d) break;
}
ll tmp = calc(v, rest);//最后“半圈”的最大子段和
if (m / (n / t) > 0) {//有能力走一圈以上时
if (sum > 0)
tmp = max(tmp + (ll)m / (n / t) * sum, (ll)(m / (n / t) - 1) * sum + calc(v, n / t));
else tmp = max(tmp, calc(v, n / t));
}
res = max(res, tmp);
}
return res;
}
int main() {
read(T);
for (int kase = 1; kase <= T; kase++) {
read(n), read(s), read(m), read(k);
rep(i, 0, n - 1) read(a[i]);
printf("Case #%d: %lld
", kase, max(0LL, s - solve()));
}
return 0;
}