複数の典型的なアルゴリズムを適切に組み合わせて考えることができるかを問う問題です.
一歩づつ,丁寧に考察を進めることで正解できます.
問題原案:tnodino(改題:achapi, uni_kakurenbo)
一言で言い表すならば 二分探索 in 二分探索 です.
前提として,要素の位置は操作および起伏に関係ありませんから, がグリッドである必要はありません.
操作によって の要素の値は減少しないため, を最大でいくらにできるかのみを考えればよいです.
これは「 回以内の操作によって を 以上にできるか?」という判定問題を の値を決め打って二分探索をすることによって求められます.
この判定問題は,「 を 以上にするために必要な,操作回数の最小値 は 以下か?」と言い換えることができます.
の値は, を昇順にソートして二分探索を行い,累積和を考えることで高速に求められます.
操作および起伏に要素の位置は関係ありませんから, がグリッドである必要はありません.
起伏を減少させるためには, の値を減少させるか,もしくは の値を増加させる必要があります.
しかし操作によって の要素が減少することはないので,後者のみを考えてよいです.
操作を繰り返して を最大で にできるとき,この問題の答えは となります.
以降 について考えます.
明らかに,操作回数の上限である が増えることによって, が減少することはないです.
したがって,「 回以内の操作によって を 以上にできるか?」という判定問題を, の値を決め打って二分探索を行うことで求めることが可能です.(判定問題の答えが真になる最大の が です. )
この判定問題を十分高速に解くことができれば,この方針で正解できそうです.
以降この判定問題について考えます.
「 を 以上にするために必要な,操作回数の最小値 は 以下か?」と言い換えることができます.
この を高速に求めることはできるでしょうか?
以降 を決めたときの について考えます.
のうちで, 未満である値すべてに対して操作を行って値を増加させる必要があります.
のうち, 未満である値 のそれぞれについて,
の最低 回の操作が必要です.
ただし,はじめに Increaser はマス にありますから, の場合は, を だけ減らしてよいです.
つまり,
として,
と表されます.
したがって, および の値を高速に求めることができればよいです.
以降これらについて考えます.
を つの数列に展開し,昇順にソートしたものを として, を満たす最大の を とします.
また, の累積和 を次のように定めます.
すると,明らかに であり, です.
これら および はクエリごと不変なので前計算で求めておきます.
以降 を考えます.
は単調に増加しますから, は と同様にして「 か?」という判定問題を考えて, の値を決め打って二分探索を行うことにより求めることができます.
最終的な答えを求めるために, を求めます.
を求めるために, についての判定問題を考えます.
この判定問題を解くために の値を求めます.
を求めるために の値を求めます.
を求めるために,二分探索を自力で実装する必要はなく,各言語で用意されているライブラリ等が使える場合が多いです.
たとえば C++ ならば std::lower_bound()
が,Python ならば bisect.bisect_left()
などがあります.
を求めるための二分探索については,各自で実装するのが一般的です.
探索する の範囲は, 以上 以下のものに限っても十分です.
この範囲に絞った場合, が を超えることがないため,答えを求める際に との をとる必要がなくなります.
ただし実際には, の値によっては,操作後の の値は,操作前の の値よりも大きくなり得ます.
および を求めるための前計算に 時間かかります.
を求めるために, 回の判定問題を解く必要があります.
一つの判定問題当たりの時間計算量は, を求めるための二分探索がボトルネックとなり です.
したがって,全体で 時間です.
これは,この問題の制約下で十分に高速です.
もともと,原案時点では,移動コストが距離に比例する前提で max A の max を求める問題でした.(茶diff?)
そこから,min A の max を求める問題となり,
それが巡回セールスマン問題を含むので NP-Hard だったため,
移動コストから距離の要素を取り除き,(Bit-DP は実装が重そうだったので)
最終的に,max A - min A の min を求める問題となりました.
がグリッドで与えられることと,Increaser の初期位置が盤面上にあることはこの名残です.
グリッドの方が問題文の表現がしやすいということで,そのままになっています.