Max GCD(贪心、枚举)
题意
给定一个长度为的整数序列:
你可以执行如下操作次:
- 选择两个整数和满足,将加,减(可能会出现负数)。
问最大的正整数,满足在次操作内,把所有变成的倍数(为任意正整数的倍数)
题目链接:https://atcoder.jp/contests/abc136/tasks/abc136_e
数据范围
思路
看到这道题,我的第一反应是二分答案。但是仔细分析一下,发现不满足二分性质。一个较小的数可能不满足要求,但是另一个较大的数有可能是满足要求的,因此不能二分。
但是枚举可能的答案,然后check应该是一个正确的思路,因此需要找一个候选答案集合。
我们考虑到,经过若干操作之后,序列的和()是不变的。并且,所有都为的倍数,那么也应该为的倍数。因此,所有的因数为候选答案集合,集合元素个数为。
下面考虑如何check答案,这是一个经典贪心问题。假设当前枚举到的答案为,将对取模,然后从小到大排序。我们希望接近于的数都减到,接近于的数都加到。因此,我们枚举分界点,分界点左边的元素全都减到,分界点右边的元素全都加到。判断一下减的次数与加的次数是否相等,以及是否即可。这可以通过前缀和快速实现。
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】