ABC136E - Max GCD
問題概要
長さの整数列とが与えられる。
以下の操作を回まで行い、整数列を加工できる。
- を満たすを選び、に 1 を足し、から 1 を引く。
この操作によって整数列を加工後、の共通の約数で最大のものを求めよ。
制約
解法
この問題のポイントは以下の 2 つ。
- どのような性質を満たす数が解の候補となるか
- 解の候補となった数が条件を満たすことをどのように判定するか
どのような性質を満たす数が解の候補となるか
加工後の整数列をとし、性質を見てみる。 整数列に対して行える操作は、どこかを 1 増やし、どこかを 1 減らすことなので、数列全体の値の合計は変化しない。よってが成り立つ。
また、全てのはこの問題の解となるある数の倍数になっているので、適当な数をとするとと表すことができる。よって、となる。
このことから、求めたい数は、の総和の約数の中にあることが分かった。
自然数の約数は、下記のような方法でで列挙することができる。
fn enumerate_divisor(n: usize) -> Vec<usize> {
let mut i = 1;
let mut divisors = vec![];
// 2乗してn以下になる数を列挙する
while i * i <= n {
// その数iがnを割り切る場合、iとn/iはnの約数となる
if n % i == 0 {
divisors.push(i);
divisors.push(n / i);
}
i += 1;
}
divisors
}
条件を満たすことをどのように判定するか
解の候補を用意した時、全てのをの倍数にするために何回操作が必要なのかを考える。これが分かれば、解の候補を大きい順に見ていき、回以下の操作で作ることができることが判明したらその時のが求める答えとなる。
まずは問題で定義されている操作を無視し、に何かを足したり引いたりして最小の増減での倍数に変形するためにはどうすればいいか考える。これは、をで割った時の余りが 0 になるようにうまく値を調整することで下記の 2 通りのどちらかで実現できる。
- から何かを引いての倍数にする場合は、の値をから引く。 (以後 マイナス 型と呼ぶ)
- 逆に に何かを足しての倍数にする場合は、をに足す。 (以後 プラス 型と呼ぶ)
このようにの倍数にするために必要な最小の増減量は分かったが、問題で定義されている操作と等価にするには、全てのに対する増減の総和が 0 になっている必要がある。次はどうやってこの帳尻を合わせるのかを考える。
をとなる数とした時、となる。 マイナス 型であれば、プラス 型であればである。マイナス 型と プラス 型の違いはの部分のみなので、は、プラス 型を選択した回数をとして
と表されることが分かる。がの約数という前提で考えると、となるあるに対して
が成り立つ。このはをで割ることで求めることができる。
となるため、とすればよい。これでプラス型を選択する回数を特定できた。
あとはどので合計回のプラス型を選択するかだが、これは単にが小さいもの、つまりが大きい順にを並べた時の最初の個とすればよい。
プラス型かマイナス型のを記録しておくことで、「を満たすを選び、に 1 を足し、から 1 を引く」操作の回数も分かるので、回以下の操作で全てのをの倍数にできるかを判定できる。
まとめ
の約数が解の候補となる。解の候補である各に対し、回の操作で各が構成できるかを調べる。調べる方法は下記の通り。
- を計算し、必要なプラス 型の回数を調べる。
- が大きい順にを並べ、その順に個のを足し合わせたものが必要な操作の回数になる。
必要な操作の数が回以下の最大のが解となる。