给出长度为n的数组,目标快乐值s,体力m,跳跃间隔k
a[i]表示i这个点的快乐值,每花费一体力跳到(i+k)%n的位置上可以获得a[(i+k)%n]的快乐值,问要达到目标快乐值s,初始至少需要多少快乐值,实际上就是求花费m体力最多能获得多少快乐值
由于跳跃间隔固定为k,那么所有的循环种数就是gcd(n,k),每个循环节的长度就是n/gcd(n,k)
所以暴力每个循环节,问题转化为最大子段和
但是存在两种情况
一种是先跳[m/len]圈,再跑m%len长度的字段和
另一种是先求出len范围内最大字段和的长度k,在跳[(m-k)/len]圈
取这两种情况的最大值
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mx = 20005;
const int inf = -inf;
ll n, s, m, k, val[mx], num[mx], sum[mx], Q[mx];
bool vis[mx];
vector <int> ways[mx];
ll gcd(ll a,ll b){
return b?gcd(b,a%b):a;
}
ll solve(ll num[], ll sum[], int len, int k, ll &ansl, ll &ansr) {//最大子段和模板
ll ans = -inf;
int front = 0, rear = 0;
for (int i = 1; i <= len+k; i++) {
while (front < rear && sum[i-1] < sum[Q[rear-1]]) rear--;
Q[rear++] = i-1;
while (front < rear && i-Q[front] > k) front++;
if (sum[i] - sum[Q[front]] > ans) {
ans = sum[i] - sum[Q[front]];
ansl = Q[front]+1;
ansr = i;
}
}
return ans;
}
int main() {
int T, kase = 0;
scanf("%d", &T);
while (T--) {
for (int i = 0; i < mx; i++) ways[i].clear();
memset(sum, 0, sizeof(sum));
scanf("%lld%lld%lld%lld", &n, &s, &m, &k);
for (int i = 0; i < n; i++) {
scanf("%lld", &val[i]);
}
int g = gcd(n, k);
for (int i = 0; i < g; i++) {
memset(vis, false, sizeof(vis));
int pos = i;
while (!vis[pos]) {
ways[i].push_back(pos);
vis[pos] = true;
pos = (pos + k) % n;
}
}
ll res = -inf;
for (int i = 0; i < g; i++) {
int k = 0, sz = ways[i].size();
int len = min(n/g, m);
for (int j = 1; j <= 2*sz; j++) {
if (j <= sz) num[j] = val[ways[i][j-1]];
else num[j] = num[j-sz];
sum[j] = sum[j-1] + num[j];
}
ll one = 0;
for (int j: ways[i]) one += val[j];
ll ansl, ansr;
ll ans1 = solve(num, sum, sz, len, ansl, ansr);
ans1 = max(ans1, ans1 + (m-ansr+ansl-1)/(n/g)*one);
ll ans2 = max(0LL, m/(n/g)*one);
ans2 = max(ans2, ans2 + solve(num, sum, sz, m%(n/g), ansl, ansr));
res = max(res, max(ans1, ans2));
}
res = max(0LL, res);
printf("Case #%d: %lld\n", ++kase, max(0LL, s-res));
}
return 0;
}