HDU6444
题解
- 环的大小为n,每次跳k步,所有有gcd(n,k)个不同的环,每个环的大小为len = n / gcd(n,k)。接下来对每一个环进行分析。
- 先对环的权值求和sum。
- 如果sum ≤0,那么找长度不大于len的最大连续字段和。
- 如果sum >0,num = m / len表示完整的圈的个数,b = m % len表示剩下的步数。有两种方案。
- 一种是先跑完num个圈,再找长度不大于b的最大连续字段和。
- 另外一种是先跑完num-1个圈,再找长度不大于len的最大连续字段和。
- 取上面两种的最大值。
- 求最大连续字段和用单调队列求解。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll const inf = 0x7f7f7f7f7f7f;
int const N = 100000 + 10;
int n,m,k,len,deq[N];
ll s,a[N],sum[N<<1],b[N<<1];
ll getsum(int n,int m){ //求长度不超过m的最大连续字段和
int s = 1,t = 1;
ll ans = -inf;
deq[t++] = 0;
for(int i=1;i<=n;i++){
while(s < t && sum[deq[t-1]] > sum[i]) t--;
deq[t++] = i;
ans = max(ans,sum[i] - sum[deq[s]]);
if(i - deq[s] == m) s++;
}
return ans;
}
ll solve(int s){
int cnt = 0,p = s;
while(cnt != len){
b[++cnt] = a[p];
p = (p + k) % n;
}
for(int i=1;i<=cnt;i++) b[i+cnt] = b[i];
for(int i=1;i<=2*cnt;i++) sum[i] = sum[i-1] + b[i];
if(sum[cnt] <= 0) return getsum(cnt<<1,len); //求长度不超过m的最大连续字段和
else{
int num = m / len, b = m % len; //num表示完整圈的个数,b表示剩下的一段
if(num > 0){
ll res1 = num * sum[cnt] + getsum(cnt<<1,b);
ll res2 = (num - 1) * sum[cnt] + getsum(cnt<<1,len);
return max(res1,res2);
}else return getsum(cnt<<1,b);
}
}
int main(){
int T,caser = 0;
scanf("%d",&T);
while(T--){
scanf("%d%lld%d%d",&n,&s,&m,&k); //圈的大小为n,每次跳k个,最多可以取m次,要求价值总和至少为s
for(int i=0;i<n;i++) scanf("%lld",&a[i]);
int num = __gcd(n,k); //圈的个数
len = n / num; //len表示每个圈的长度
ll res = -inf;
for(int i=0;i<num;i++) res = max(res,solve(i));
printf("Case #%d: %lld\n",++caser,res >= s ? 0 : s - res);
}
return 0;
}