Description
给出 n n n个数 a 0 , . . . , a n − 1 a_0,...,a_{n-1} a0,...,an−1顺序围成一圈,任选起点,每次可以从 a i a_i ai跳到 a ( i + k ) % n a_{(i+k)\%n} a(i+k)%n位置,至多跳 m m m次,问所经过的位置的值之和至少需要加多少才能达到 s s s,如果该值已经不小于 s s s则输出 0 0 0
Input
第一行一整数 T T T表示用例组数,每组用例首先输入四个整数 n , s , m , k n,s,m,k n,s,m,k,之后输入 n n n个整数 a i a_i ai
( T ≤ 50 , 1 ≤ n ≤ 1 0 4 , 1 ≤ s ≤ 1 0 18 , 1 ≤ m ≤ 1 0 9 , 1 ≤ k ≤ n , − 1 0 9 ≤ a i ≤ 1 0 9 ) (T\le 50,1\le n\le 10^4,1\le s\le 10^{18},1\le m\le 10^9,1\le k\le n,-10^9\le a_i\le 10^9) (T≤50,1≤n≤104,1≤s≤1018,1≤m≤109,1≤k≤n,−109≤ai≤109)
Output
输出答案
Sample Input
2
3 10 5 2
3 2 1
5 20 6 3
2 3 2 1 5
Sample Output
Case #1: 0
Case #2: 2
Solution
从 i i i位置出发,每次跳 k k k步所能经过的位置编号必然为 a ( i + t ⋅ g c d ( k , n ) ) % n a_{(i+t\cdot gcd(k,n))\%n} a(i+t⋅gcd(k,n))%n这 n g c d ( k , n ) \frac{n}{gcd(k,n)} gcd(k,n)n个位置,也就是说本质不同的所经位置序列只有 g c d ( k , n ) gcd(k,n) gcd(k,n)个,对于每个序列单独考虑,问题转化为从一个序列选择一个位置开始至多走 m m m步所能得到的最大和,分两种情况:
1.若该序列和 s u m < 0 sum<0 sum<0,那么不会一直遍历这个序列,至多只会走 m i n ( m , l e n ) min(m,len) min(m,len)步,其中 l e n = n g c d ( k , n ) len=\frac{n}{gcd(k,n)} len=gcd(k,n)n为序列长度
2.若该序列和 s u m ≥ 0 sum\ge 0 sum≥0,那么前期会不断重复遍历这个序列,但是后面若干步需要单独考虑,要么是循环走 ⌊ m l e n ⌋ \lfloor\frac{m}{len}\rfloor ⌊lenm⌋次,最后至多走 m % l e n m\% len m%len步,要么是循环走 ⌊ m l e n ⌋ − 1 \lfloor\frac{m}{len}\rfloor-1 ⌊lenm⌋−1次,最后至多走 l e n len len步(需要考虑这种情况是因为对于最后 l e n + m % l e n len+m\% len len+m%len步,不一定是走一个循环再走若干步最优,例如 1 1 − 1 − 1 1 1\ 1\ -1\ -1\ 1 1 1 −1 −1 1走六步,如果只取前两个 1 1 1和最后一个 1 1 1所得答案是 3 3 3,但是如果走一个循环拿到 1 1 1再走一步拿一个 1 1 1的话答案只有 2 2 2)
故剩余问题为给出一个序列,求该序列的长度不超过 x x x的最大循环子段和,由于 x x x不会超过序列长度,故把序列复制之后可以把环上问题变成链上问题,求出该序列前缀和后,用单调队列维护递增序列,那么当前值插入之后,队首元素即为前面经过值中的最小值,两者做差维护答案即可
Code
#include
#include
using namespace std;
typedef long long ll;
const int maxn=20005;
int a[maxn],que[maxn];
ll sum[maxn];
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
ll Solve(int n,int m)
{
ll ans=0;
int st=1,ed=0;
for(int i=1;i<=n;i++)
{
while(st<=ed&&i-que[st]>m)st++;
while(st<=ed&&sum[que[ed]]>sum[i])ed--;
if(st<=ed)ans=max(ans,sum[i]-sum[que[st]]);
que[++ed]=i;
}
return ans;
}
int main()
{
int T,n,m,k,Case=1;
ll s;
scanf("%d",&T);
while(T--)
{
scanf("%d%lld%d%d",&n,&s,&m,&k);
for(int i=0;i=len)
{
res=sum[len]*(m/len-1)+Solve(2*len,len);
ans=max(ans,res);
}
}
}
printf("Case #%d: %lld\n",Case++,max(0ll,s-ans));
}
return 0;
}