要想A一道题,你需要正版ide,正版草稿纸,正版键盘,正版双手,但最重要的是正版大脑.(改编自wxf在微积分群的吐槽)
分析一下题意,选的所有数的下标模gcd(n,k)都是同余的,而且是在所有同余的下标上循环.
对不同余数的下标分别处理,分别转换为在一串长度为N=n/gcd(n,k)的数上一格一格往右边走,走到头循环回来时,走m步最多能得到多大的和.
考虑对所有数字求个和sum.如果sum<0,那么最优方案走的步数不会超过N(否则少走连续N步一定使答案变优),只需要单调队列求带长度限制的最大区间和.
如果sum>0,那么只要m>N,则最优方案走的步数不会少于m-N步(否则多走N步使得答案变大)
sum>0的时候解题的想法还是要转化为求一个最大区间和.以走m/N*N步的方案为基准(这个方案不管从哪里开始走都得到相同的答案),考虑多走或者少走几步,但是总步数在[m-N,m]变化.多走几步的情况就是找一个带长度限制的最大区间和,少走几步的情况就是找一个带长度限制的最小区间和把它从答案里去掉.都是单调队列就能搞定.借助函数指针可以把最大区间和以及最小区间和写成一个函数.
#include<cstdio>
typedef long long ll;
const int maxn=10005;
ll max(ll a,ll b){
return a>b?a:b;
}
ll min(ll a,ll b){
return a<b?a:b;
}
int a[maxn],b[maxn];
ll pre[maxn<<1];
int gcd(int a,int b){
return (b==0)?a:gcd(b,a%b);
}
int q[maxn<<1];
int head=0,tail=0;
void push(int x,ll cmp(ll,ll)){
while(head!=tail&&(cmp(pre[x],pre[q[tail-1]])==pre[q[tail-1]]))tail--;
q[tail++]=x;
}
void pop(int lim){
while(head!=tail&&q[head]<=lim)head++;
}
ll get_extreme(int N,int M,ll cmp(ll,ll)){
ll ans=0;head=tail=0;
push(0,cmp);
for(int i=1;i<=N;++i){
ans=cmp(ans,pre[i]-pre[q[head]]);
push(i,cmp);
pop(i-M);
}
return ans;
}
ll calc(){
int n;ll s;int m,k;
scanf("%d%lld%d%d",&n,&s,&m,&k);
for(int i=0;i<n;++i)scanf("%d",a+i);
int period=gcd(n,k);
int N=n/period;
ll ans=0;
for(int i=0;i<period;++i){
for(int j=i,p=0;p<N;j=(j+k)%n){
b[++p]=a[j];
}
pre[0]=0;
for(int j=1;j<=N;++j)pre[j]=pre[j-1]+b[j];
for(int j=1;j<=N;++j)pre[j+N]=pre[j]+pre[N];
if(pre[N]<=0||m<=N){
ans=max(ans,get_extreme(2*N,min(N,m),max));
}else{
int mm=m/N*N;
int hidlt=m-mm;
int lodlt=N-hidlt;
ans=max(ans,m/N*1ll*pre[N]+get_extreme(2*N,hidlt,max));
ans=max(ans,m/N*1ll*pre[N]-get_extreme(2*N,lodlt,min));
}
}
return max(0,s-ans);
}
int main(){
int T;scanf("%d",&T);
for(int i=1;i<=T;++i){
printf("Case #%d: %lld
",i,calc());
}
return 0;
}