競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 188F

ABC188F

操作の逆を考える. \(f(y) := y / 2 \ (\text{if}\ y \equiv 0 \ mod \ 2)\), \(g_{+}(y) := y + 1\), \(g_{-}(y) := y - 1\). これらの操作で \(Y\) から \(X\) を作ることを考える. 操作 \(f\) のおかげで,小さくしていくので計算量の見積りもし易い. \(O(log Y)\) 程度になることが期待できる. そのためには, \(g_{+}, g_{-}\) の操作をまとめて行いたい.

操作の性質
\(g_{+}, g_{-}\) は連続で行うメリットがない. また,\(g_{+}\) を 2回以上連続で行った後に \(f\) を行うのは, 先に \(f\) を行ったあとに \(g_{+}\) を何回かすることで 操作の回数を減らせる. よって,基本的には \(f, g_{+} or g_{-}\) の繰り返しになる. その前後に \(g_{*}^{k}\), \(* \in \{+, -\}, k \in \mathbb{N}\) を掛けた形にが最善.
\(y\) における答えを \(a_{y}\) とおく. 2で割る操作 \(f\) をある程度行ったあとは, \(g_{+}\) or \(g_{-}\) を連続で繰り返すことになる. よって基本的には,

  • \(y \equiv 0\) のとき: \(a_{y} = min(abs(X-y), a_{y}/2 + 1)\)
  • \(y \not\equiv 0\) のとき: \(a_{y} = min(abs(X-y), a_{y+1}/2 + 2, a_{y-1}/2 + 2)\)

となる. ただし,このまま再帰で \(a\) を求めると無限ループになる. \(y = 0, y = 1\) だけ個別に求めておく. これらの場合は,\(f\) を用いるメリットが無いので \(abs(X-y)\) が答えとなる.

使っている記号,マクロ等 "https://ecsmtlir.hatenablog.com/entry/2022/12/23/131925"

int main() {
  ll X, Y; cin >> X >> Y;

  map<ll,ll> memo;
  auto f = [&](auto f, ll y) -> ll {
    if(y == 0) return abs(X-y);
    if(y == 1) return abs(X-y);
    if(y == X) return 0;
    if(memo.find(y) != memo.end()) return memo[y];

    ll ret;
    if(y % 2 == 0){
      ret = min(abs(X-y), f(f,y/2) + 1);
    }else{
      ret = min(abs(X-y), min(f(f,(y+1)/2)+2, f(f,(y-1)/2)+2));
    }
    return memo[y] = ret;
  };
 
  cout << f(f,Y) << endl;
 
  return 0;
}