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;
}
posted @ 2022-06-14 10:54  pbc的成长之路  阅读(41)  评论(0编辑  收藏  举报