题目大意
给定一个长度为n的数组,有m组查询,每组查询包含一个正整数xi,初始指针指向a[1],每个一个单位时间指针向后移动,即a[i]移动一个位置到达a[i+1],a[n]移动一个位置后到达a[1],要使得所有数字之和>=xi, 问至少移动多少次?如果无法达到>=x,输出-1
思路
设sum[i] 表示a[1]+a[2]+…+a[i](即前缀和),maxx[i]表示max{sum[1],sum[2],…,sum[i]}显然maxx是非递减序列.
如果maxx[n]>=x, 说明答案一定在1 ~ n,根据maxx[i]非递减性质可以使用二分找到第一个下标i满足maxx[i]>=x.
如果maxx[n]<x:
1)如果sum[n]<=0,并且maxx[n]<x,循环一圈之后答案总和反而减少,说明永远不可能到达x,故输出-1
2)如果sum[n]>0:
sum[i+n]=sum[i]+sum[n];
设d=x-maxx[n],要使得答案>=x,那么至少要循环ceil(d/sum[n])圈
由于第一圈以内答案到达maxx[n]后都需要循环ceil(d/sum[n])圈,显然其余maxx[i]也至少需要循环 ceil(d/sum[n])圈,所以可以找到最小的maxx[i](maxx[i]=x-ceil(d/sum[n]) *sum[n])使得让它循环ceil(d/sum[n])圈后>=x,二分查找下标i即可。这样最后的答案就是ceil(d/sum[n]) *sum[n]+i.注意问的是移动多少次,所以答案要-1.
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+50;
ll n,m,T,a[maxn],sum[maxn],maxx[maxn],ans[maxn];
int main(){
cin>>T;
while(T--){
cin>>n>>m;
memset(maxx,0,sizeof(ll)*(n+10));
for(int i=1;i<=n;i++)cin>>a[i],sum[i]=sum[i-1]+a[i],maxx[i]=max(maxx[i-1],sum[i]);
for(int i=1;i<=m;i++){
ll x;cin>>x;
if(x<=maxx[n]){
cout<<lower_bound(maxx+1,maxx+1+n,x)-maxx-1<<' ';
}
else{
if(sum[n]<=0){
cout<<-1<<' ';
}
else{
ll d=x-maxx[n];
ll tot=ceil(d*1.0/sum[n]);
x-=tot*sum[n];
ll t=lower_bound(maxx+1,maxx+1+n,x)-maxx;
t=t+tot*n-1;
cout<<t<<' ';
}
}
}
cout<<endl;
}
return 0;
}