問題はこちら
から となる を選び、 とする。この時、 以下の操作回数で の最大公約数として考えられるもののうち、最大のものを求めよ。
考え方
step1 - 解の候補
まず、 に+1して、 に-1するという操作は、 の値を一つ に移動する操作と考えることができる。
次に大事なのは、答えの候補は の和の約数 となること。これはなぜかを考えてみる。今、答えとなる最大の最大公約数を とする。
- 約数であるという性質から、
- modの性質から、
- 今回の操作は片方に+1, もう片方に-1するだけなので、総和は操作終了後にも変わらない。
- 以上より、答えの候補は の和の約数となる。
なので、ある最大公約数 が 回以下の操作によって達成されるかをすべて確かめ、達成されるものの中で最大のものが答えとなる。
step2 - 達成可能かを確認する
で割ったあまりが小さい方(= )から、大きい方(= )へと渡すのが操作方法が少なくて良い方法だとわかる。 , , とおく。(簡単にするため、二つの数字でやり取りすることで割り切れる場合を考えてみる。)
- から へ渡して で割り切れるようにする時。操作回数は
- から へ渡して で割り切れるようにする時。操作回数は
- より、
よって、 で割った余りを小さい順でsortして、インデックス より小さい側を渡す側。インデックス 以上を渡される側として、必要な操作回数が 以下であれば満たすと判定すれば良い。
解答
#include <bits/stdc++.h> using namespace std; using ll = long long; #define rep(i, n) for(ll i = 0; i < (ll)(n); i++) #define repr(i, n) for(ll i = ll(n - 1); i >= 0; i--) #define each(i, mp) for(auto& i:mp) #define all(obj) (obj).begin(), (obj).end() /* ------------- ANSWER ------------- */ /* ---------------------------------- */ // 約数列挙 vector<ll> divisor(ll n) { vector<ll> res; for (ll i = 1; i * i <= n; ++i) { if (n % i == 0) { res.emplace_back(i); if (i != n / i) res.emplace_back(n / i); } } return res; } int main() { ll n, k; cin >> n >> k; vector<ll> a(n); rep(i, n) cin >> a[i]; vector<ll> divs = divisor(accumulate(all(a), 0LL)); ll ans = 0; each(e, divs) { vector<ll> mods(n); rep(i, n) mods[i] = a[i] % e; sort(all(mods)); vector<ll> minus(n + 1); rep(i, n) minus[i + 1] = minus[i] + mods[i]; vector<ll> plus(n + 1); repr(i, n) plus[i] = plus[i + 1] + e - mods[i]; for (ll i = 0; i <= n; ++i) { if (max(plus[i], minus[i]) <= k) ans = max(ans, e); } } cout << ans << '\n'; return 0; }