Max GCD(贪心、枚举)
题意
给定一个长度为\(N\)的整数序列:\(A_1, A_2, \dots, A_N\)
你可以执行如下操作\(0 \sim K\)次:
- 选择两个整数\(i\)和\(j\)满足\(i \neq j\),将\(A_i\)加\(1\),\(A_j\)减\(1\)(可能会出现负数)。
问最大的正整数\(x\),满足在\(K\)次操作内,把所有\(A_i\)变成\(x\)的倍数(\(0\)为任意正整数的倍数)
题目链接:https://atcoder.jp/contests/abc136/tasks/abc136_e
数据范围
\(2 \leq N \leq 500\)
\(1 \leq A_i \leq 10^6\)
\(0 \leq K \leq 10^9\)
思路
看到这道题,我的第一反应是二分答案。但是仔细分析一下,发现不满足二分性质。一个较小的数可能不满足要求,但是另一个较大的数有可能是满足要求的,因此不能二分。
但是枚举可能的答案,然后check应该是一个正确的思路,因此需要找一个候选答案集合。
我们考虑到,经过若干操作之后,序列的和(\(s = \sum\limits_{i=1}^N A_i\))是不变的。并且,所有\(A_i\)都为\(x\)的倍数,那么\(s\)也应该为\(x\)的倍数。因此,所有\(s\)的因数为候选答案集合,集合元素个数为\(O(\sqrt{N \max A_i})\)。
下面考虑如何check答案,这是一个经典贪心问题。假设当前枚举到的答案为\(x\),将\(A_i\)对\(x\)取模,然后从小到大排序。我们希望接近于\(0\)的数都减到\(0\),接近于\(x\)的数都加到\(x\)。因此,我们枚举分界点,分界点左边的元素全都减到\(0\),分界点右边的元素全都加到\(x\)。判断一下减的次数与加的次数是否相等,以及是否\(\leq K\)即可。这可以通过前缀和快速实现。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 510;
int n, k;
int a[N], b[N];
ll sum[N];
bool check(int x)
{
for(int i = 1; i <= n; i ++) b[i] = a[i] % x;
sort(b + 1, b + n + 1);
for(int i = 1; i <= n; i ++) sum[i] = sum[i - 1] + b[i];
for(int i = 1; i <= n; i ++) {
ll t = (ll)x * (ll)(n - i);
if(sum[i] == t - (sum[n] - sum[i]) && sum[i] <= k) {
return true;
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &k);
ll tot = 0;
for(int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
tot += a[i];
}
int ans = 1;
for(int i = sqrt(tot); i >= 1; i --) {
if(check(i)) ans = max(ans, i);
if(check(tot / i)) ans = max((ll)ans, tot / i);
}
printf("%d\n", ans);
return 0;
}