题目大意
给一个长度为 n n n的正整数序列 A = { a 1 , a 2 , … a n } A=\{a_1,a_2,\dots a_n\} A={a1,a2,…an},你可以做如下的操作,最多做 k k k次,最少做 0 0 0次。
- 选择 i ∈ { 1 , 2 , … n } i\in\{1,2,\dots n\} i∈{1,2,…n},将 a i a_i ai加 1 1 1
求 gcd ( a 1 , a 2 , … a n ) \gcd(a_1,a_2,\dots a_n) gcd(a1,a2,…an)的最大的可能值。
2 ≤ n ≤ 3 × 1 0 5 , ≤ k ≤ 1 0 18 , 1 ≤ a i ≤ 3 × 1 0 5 2\leq n\leq 3\times 10^5,\leq k\leq 10^{18},1\leq a_i\leq 3\times 10^5 2≤n≤3×105,≤k≤1018,1≤ai≤3×105
题解
令所有 a i a_i ai的和为 s u m sum sum,最大的 a i a_i ai值为 m x mx mx。
显然,若 k ≥ n × x − s u m k\geq n\times x-sum k≥n×x−sum,那么我们可以先把每个 a i a_i ai的值加到 m x mx mx,然后不断进行操作,每次操作将每个 a i a_i ai都加 1 1 1,一直操作到不能操作为止。此时的答案为 m x + ⌊ k − ( n × x − s u m ) n ⌋ mx+\lfloor\dfrac{k-(n\times x-sum)}{n}\rfloor mx+⌊nk−(n×x−sum)⌋。
如果 k < n × x − s u m k<n\times x-sum k<n×x−sum,那么无论加多少次 1 1 1, a i a_i ai的最小值一定不会超过 m x mx mx,所以 gcd ( a 1 , a 2 , … a n ) \gcd(a_1,a_2,\dots a_n) gcd(a1,a2,…an)也不会超过 m x mx mx,我们考虑枚举答案。
假设当前枚举的 gcd \gcd gcd为 d d d,则我们需要将每个 a i a_i ai增至 a i a_i ai为 d d d的倍数。
因为 a i a_i ai最大只有 3 × 1 0 5 3\times 10^5 3×105,所以我们可以用桶来存 a i a_i ai。
枚举 d d d的倍数 j j j,则所有满足在 [ j − d + 1 , j ] [j-d+1,j] [j−d+1,j]这一块中的 a i a_i ai都应该加到 j j j。对于这样的每一块,我们只需要求出将这一块中所有 a i a_i ai加到 j j j的操作次数,最后将每一块的操作次数求和。如果和小于等于 k k k,则可以使最大公约数为 d d d,否则不行。
对于区间元素数量和区间和,用前缀和来维护即可。
最坏的情况下,需要枚举次数为 m x + m x 2 + ⋯ + 1 = ∑ i = 1 m x m x i ≈ m x ln m x mx+\dfrac{mx}{2}+\cdots+1=\sum\limits_{i=1}^{mx}\dfrac{mx}{i}\approx mx\ln mx mx+2mx+⋯+1=i=1∑mximx≈mxlnmx,所以时间复杂度为 O ( m x ln m x ) O(mx\ln mx) O(mxlnmx)。
code
#include<bits/stdc++.h>
using namespace std;
int n;
long long k,sum=0,mx=0,v,a[300005],z[300005],s[300005];
int main()
{
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum+=a[i];mx=max(mx,a[i]);
}
if(k>=mx*n-sum){
k-=mx*n-sum;
mx+=k/n;
printf("%lld",mx);
return 0;
}
for(int i=1;i<=n;i++) ++z[a[i]];
for(int i=1;i<=mx;i++){
s[i]=s[i-1]+z[i]*i;
z[i]+=z[i-1];
}
for(int i=mx;i>=1;i--){
v=0;
for(int j=i;j<=mx;j+=i){
v+=1ll*(z[j]-z[j-i])*j-(s[j]-s[j-i]);
if(v>k) break;
}
v+=1ll*(z[mx]-z[mx/i*i])*(mx/i*i+i)-(s[mx]-s[mx/i*i]);
if(v<=k){
printf("%d",i);
break;
}
}
return 0;
}