なにかの記録

ゆるふわ競技プログラミング

AtCoder ABC 136 E - Max GCD

問題概要

長さNの整数列A_1 ... A_Nが与えられる。次の操作を0回以上K回以下行うことができる。

  • i≠jなる1以上N以下の2つの整数i,jを選び、A_i1を足し、A_j-1を足す。この操作の後いずれかの要素が負になってもよい。

操作後のAの全ての要素を割り切る正の整数として考えられる値の最大値を求めよ。

制約

  • 2≤N≤500
  • 1≤A_i≤10^ 6
  • 0≤K≤10^ 9

解説/考察

ステップ1

答えをz、各A_iに対して変化させる値をx_iとおくと、

(A_i+x_i)\equiv 0 \,(mod \,z)

です。よって

\sum _ {i=1}^N (A_i+x_i) \equiv 0 \,(mod \,z)

が成り立ちます。またこのとき、問題文より

\sum _ {i=1}^N (x_i) = 0

なので、

\sum _ {i=1}^N (A_i) \equiv 0 \,(mod \,z)

と変形できます。よって、答えzAの総和の約数であることが分かります。

ステップ2

次に、答えの候補をdとしたときに、K回以内の操作で整数列Aの要素をdの倍数にすることができるのか判定する方法を考えます。
A_iについて、

  • A_i \, mod \,\, dを引く(「-方向」)
  • d-(A_i \, mod \,\, d)を足す(「+方向」)

ことによってdの倍数にすることができます。S= (「-方向」の量) - (「+方向」の量)とするとき、S=0となっている必要があります。
まず最初に、Aの全ての要素を「-方向」に設定してみると、

 S_0 = \sum _ {i=1}^N (A_i \, mod \,\, d)

です。また、これはdの倍数となることがAの総和がdの倍数であることからわかります。
それでは、ここから1つの要素を「+方向」に変えてみます。このとき、Sの変化量は

 -(A_i \, mod \,\, d)-(d-(A_i\,mod\,\,d)) = -d

となります。よって、S_0/d 個だけ「+方向」に設定しなおせばよいことがわかります。操作回数は「+方向」に設定しなおした後に残った「-方向」の量です。どの要素を設定しなおしてもSの変化量は-dなので、S_0から、\lbrace A_i \, mod \,\, d \rbrace のうち大きい方からS_0/d個の和を引いた値が操作回数の最小値となります。これによって求まった値がK以内ならば、Aの要素をdの倍数にすることができます。この判定方法をステップ1で挙げた答えの候補のうち大きい方から適用していくことで答えが求まります。

この記事に沿ったソースコードの例です。

思考の流れ

  • とりあえず文字でおいて式化してみる。

これはあらゆる問題で言えることですが、未知数を一つの文字で置いて、式の最終的な状態、最初の状態、条件式を書き出してみると重要なことが見えてくることが多いです。この問題で言うと、最初の変化量をx_iと置くことで、最後の状態A_i+x_iの式と条件式から、関係性を見出すことができます。

  • 答えはAの総和の約数で、約数の個数はあまり大きくならない(参考)ので、列挙して一つずつチェックしていけば良い。判定方法を考える。
  • A_idの倍数にしたいとき、A_i\,mod \,\,dだけ引く、又はd-(A_i\,mod \,\,d)を足せばよいことがわかり、全体でこれをうまく調整することで「引く量」=「足す量」にしたい。
  • 「引く量」と「足す量」の差を考えれば良さそう。式変形(あるいは実験)をすると、差がdの倍数かつ、要素を一つ変えるごとに常に変化量がdであることから、答えにたどり着く。

感想

コンテスト中、ステップ1を思いついたときは、これがこの問題の本質だと思っていましたが、どちらかというとステップ2の判定フェーズの方が骨が折れます。しかしステップ1に気付かなければ何も始まらなく、非常に考察力が試される問題だと思いました。