キーサイト・テクノロジープログラミングコンテスト(AtCoder Beginner Contest 454)E,F,G問題メモ

E - LRUD Moving

問題文

  • $N\times N$ のグリッドがあります。上から $i$ 行目、左から $j$ 列目のマスをマス $(i,j)$ と表記します。
  • はじめ、コマがマス $(1,1)$ に置かれています。
    • このコマを上下左右に隣接するマスに動かす移動を $N^2-2$ 回繰り返すことで、マス $(A,B)$ 以外の全てのマスを経由しつつマス $(N,N)$ までコマを動かしたいです。
    • ただし、同じマスを $2$ 回以上経由してはいけません(マス $(1,1),(N,N)$ も途中で経由してはいけません)。
  • そのような移動が可能か判定し、可能である場合は移動列を一つ出力してください。
  • $T$ 個のテストケースが与えられるので、それぞれについて答えを求めてください。

制約

  • $1\le T \le 5000$
  • $2\le N\le 10^3$
  • $1\le A,B\le N$
  • $(A,B)\neq (1,1),(N,N)$
  • 全てのテストケースにおける $N^2$ の総和は $10^6$ 以下
  • 入力される値は全て整数

解法

手で解くのは簡単だが、アルゴリズムに落とし込めますか、という問題。

可能不可能の条件

まず、可能不可能を判定する。
グリッド移動での考察の典型として、盤面を市松模様に塗り、黒と白の個数を数える。

$N$ の偶奇にかかわらず、$(1,1)$ と $(N,N)$ は同じ色となる。ここでは黒で塗ったとする。 この時、(起終点を含めて)経由する色は、黒が白よりちょうど1つ多くなければならない。

  • $N$ 奇数の時
    • 盤面全体で、黒が白より1つ多い。$(A,B)$ を経由しないとしたとき、それが黒でも白でも、条件を満たせない。
  • $N$ 偶数の時
    • 盤面全体で黒と白が同数である。$(A,B)$ が白の場合のみ、条件を満たせる。

よって、「$N$ が偶数で、$A+B$ が奇数」の場合以外は、条件を満たせないとわかる。

では、「$N$ が偶数で、$A+B$ が奇数」の場合は必ず可能か?
自由度が非常に高いのでおそらくは可能だろうと当たりを付け、以下で実際に構築できることを確認する。

具体的な構築

後から見れば以下の解説の方が実装しやすそうだが、とりあえず本番に考えたことをメモ。

盤面を対角線で4つに分ける。/の対角線上のマスは、①②側に属するとする。

\  ①  /
  \  /
②  ×  ④
  /  \
/  ③  \

$(A,B)$ が①にある時、以下の構築方法がある。

  • 変数 $d$ を true で初期化し、$(1,1)$ から以下を繰り返す
    • 上に進める場合、上に進む
    • そうで無い場合、$d$ が true なら右に、false なら左に進めるなら進む
    • そうで無い場合、下に進む
  • $d$ がtrueの時に右端に達した、またはfalseの時に左端に達したら、$d$ を反転させる

「進める」とは、進もうとしているマスが、盤面の外、既に通過したマス、$(A,B)$ のどれでもないことを指す。

この法則に従うと、$(A,B)$ までは蛇腹状に、それ以降は↙↘方向に $(A,B)$ を避けた影響が波及するようなルートになる。

─────┐
┌┐■┌─┘
│└─┘┌┐
└───┘│
┌────┘
└───→

この波が左右の壁に到達したら影響は吸収され、再び蛇腹状に戻る。
ただし、それより先に下の壁に到達したら、最下段で通過しないマスができてしまう。

$(A,B)$ が①にある場合は、必ずどちらの波も左右の壁に先に到達するので、この法則で構築できる。

②にある場合は、このロジックを\の対角線で反転し、優先順位を 左→上下→右 にすればよい。
③④にある場合は盤面を180度回転させて構築し、最後に元に戻せば良い。

Python3

F - Make it Palindrome 2

問題文

  • 正整数 $N,M$ と長さ $N$ の整数列 $A=(A_1,A_2,\ldots,A_N)$ が与えられます。
  • この整数列 $A$ に対して以下の操作を $0$ 回以上何回でも行うことができます:
    • $1\le l\le r\le N$ を満たす整数の組 $(l,r)$ を選び、$i=l,l+1,\ldots,r$ に対して $A_i$ を $(A_i+1) \bmod M$ で置き換える。
  • $A$ を回文にするために必要な操作回数の最小値を求めてください。
    • ただし、$A$ が回文であるとは $i=1,2,\ldots,N$ に対し $A_i=A_{N+1-i}$ が成り立つことを指します。
  • $T$ 個のテストケースが与えられるので、それぞれについて答えを求めてください。

制約

  • $1\le T$
  • $1\le N\le 2\times 10^5$
  • $1\le M\le 10^9$
  • $0\le A_i \lt M$
  • 全てのテストケースにおける $N$ の総和は $2\times 10^5$ 以下
  • 入力される値は全て整数

解法

初期状態の $A$ から、それぞれ対象な位置にある相手との差分だけ、左右どちらかを増やせばいい。

つまり、$K=\lfloor \frac{N}{2} \rfloor$ とし、$i=1,2,...,K$ に対し $B_i=A_{N-i+1}-A_{i}$ とすると、 問題は以下のように言い換えられる。

  • 以下の操作を繰り返せる。
    • $1\le l\le r\le K$ を満たす整数の組 $(l,r)$ および $d \in \{+1,-1\}$ を選び、$i=l,l+1,\ldots,r$ に対して $B_i$ を $(B_i+d) \bmod M$ で置き換える。
  • 全ての要素が $0$ である長さ $K$ の数列を、$B$ に一致させるまでの最小操作回数を求めよ。
A   3  1  4  1  5  9  2
B  -1 +8 +1 ----'  |  |
    |  `-----------'  |
    `-----------------'

ここで $d=-1$ とした操作は、元の操作では対象な位置に+1することに相当する。 元の操作では中央をまたいで区間を指定することも可能ではあるが、対象な位置に同時に+1する操作は回文を作る上で意味が無いので、またがない操作しかできないと考えて問題ない。

A   3  1  4  1  5  9  2
   |-------------|       ←この操作は、対称の位置にある 4 と 5 に同時に+1しているので
   |----|                ←この操作と等価である

で、言い換え後の問題を解くのだが、ARCとかの過去問であったような?(気のせいかも)

目標となる各 $B_i \mod{M}$ が固定値ということは、 その差分 $B_i-B_{i-1}$ の $\mod{M}$ も固定値である。

M=12
i-1    |■■■■■■←--- 7 ----→
 i     |■■■■■■■■■■■■■    Bi に ±M を好きに施しても、
     ↕                               差分 Bi-B[i-1] mod M は不変
i-1    |■■■■■■
 i     |■←- -5 -→

$B_i$ の全ての隣接要素の差分を($\mod{M}$ 上の)目標の値にできれば、そこから復元される $B_i$ もまた、目標の値になっている。
$B_0=B_{K+1}=0$ とし、$D_i=B_i-B_{i-1} \mod{M}$ とした長さ $K+1$ の数列 $D$ を定義する。

M=12            K
i   0   1   2   3   4
B   0  -1   8   1   0
D      11   9   5  11

ここで、区間 $[l,r)$ への加算操作は、差分という観点で見た場合、$D_l$ に $+1$、$D_r$ から $-1$ することに相当する。 $-1$ 操作は正負逆になる。

つまり、問題は更に以下のように言い換えられる。

  • 全ての要素が $0$ である、長さ $K+1$ の数列 $X$ を用意する
  • 「$1 \le i,j \le K+1$ なる $i,j$ を選び、$X_i$ に1加算、$X_j$ から1減算する」操作を繰り返し、$\mod{M}$ 上で $D$ と等しくするまでの最小回数を求めよ

$X$ が以下のようになったらいい。

  • $\sum X = 0$
  • $X_i \equiv D_i \mod{M}$($i=1,...,K+1$)
  • そのような $X$ の中で、正の要素のみの和(=操作回数)が最小

ところで、$\sum D \equiv 0 \mod{M}$ である($B_0=0$ から $B_{K+1}=0$ までのmod上の累積差分なので)。

$\sum D = kM$ とした時、$D$ からどれか $k$ 個を選んで $X_i=D_i-M$、他は $X_i=D_i$ とすると、ちょうど $\sum X = 0$ となる。 当然、正の値を最小化するには $D_i$ の大きい方から $k$ 個を選んだ方がいい。

D     11   9   5  11    和は 36 = 3*M より、3個を選んで -M する
X     -1  -3   5  -1

これによって構築される $X$ が、操作回数最小となる。

略証

よって、$\sum D$ から、$D$ の大きい方から $\frac{\sum D}{M}$ 個を取り除いた総和が答えとなる。

Python3

G - Mode in the Subtree

問題文

  • 頂点に $1$ から $N$ の番号がついた $N$ 頂点の根付き木が与えられます。
    • 頂点 $1$ が根で、頂点 $i$ の親は頂点 $p_i$ です($p_i \lt i$)。
  • 各頂点には色が塗られていて、頂点 $i$ は色 $c_i$ で塗られています($1 \leq c_i \leq N$)。
  • $v = 1, 2, \dots, N$ について次の問題を解いてください。
    • $f_i$ を「頂点 $v$ の部分木に含まれる頂点のうち色 $i$ で塗られた頂点の個数」とします。
    • 以下の2つを求めてください
      • $f_i$ ($1 \leq i \leq N$) の最大値
      • 最大値を与える $i$ の個数
  • ただし入力は一部に疑似乱数を使い、出力はハッシュ値で答えます。(詳細は問題ページ)

制約

  • $2 \leq N \leq 2.5 \times 10^6$
  • $1 \leq p_i \lt i$
  • $1 \leq c_i \leq N$
  • 入力される値は全て整数
  • 実行時間制限: 4 sec

解法

マージテクを使って、例えば

  • $DP[v]=\{$ 色 $c$ : $v$ の部分木で色 $c$ で塗られた頂点数 $\}$

を小さい方から大きい方にマージしていくと(あと最大値を管理する追加の情報があれば)木DPで求められる。

しかし今回は $N$ が大きく時間制限が厳しい。
Dictを大量に生成するコストが重たい一因となる。

1つのデータ構造を使い回すことでこの問題を解消し、定数倍高速化する手法として、DSU on Tree というのがある、らしい。

活用シーンとしては、

  • 木上のマージテク(軽い方の頂点数に依存した計算量がかかる処理)が必要
  • かつ、例えば以下のいずれかのような状況
    • 状態を配列1つなど Dict より軽いデータ構造で管理でき、少しでも高速化したい
    • メモリ制約がきつい(上例のDPは、明示的に解放しない場合、$O(N \log{N})$ の空間を消費)
    • セグ木でデータを管理しなければならないなど、全ての頂点にDPの計算結果を記憶できない

手順としては、(上記のリンク先のスライドの方が図説でわかりやすい)

  1. 木をHL分解
    • 具体的なパスに分解する必要はなく、各頂点の子のうち最も部分木の頂点数が多い1つが分かれば良い
    • 最も部分木の頂点数が多い1つを Heavy-child、それ以外を Light-child とする。
  2. クエリに答えるために必要なデータ構造 $D$ を初期化
  3. 根からDFS。
    • ある頂点 $v$ にはじめて訪れたとする。この時、$D$ は必ず初期状態である。
    • まずは Light-child のそれぞれを先に探索する。
      • この後 $v$ に戻ってきたときも、$D$ は必ず初期状態
    • Heavy-child $w$ を探索する。
      • この後 $v$ に戻ってきたときは、$D$ には $w$ 以下の情報が登録されている。
    • 再び Light-child のそれぞれをDFSで探索し、$D$ にそれらの部分木の情報を登録する。
      • このDFSは、Heavy, Light の区別を伴う全体のDFSとは別で、シンプルに部分木を全走査(☆)して各頂点の情報を登録できればよい。
    • 最後に $v$ 自身の情報を $D$ に登録する。
    • この時点で、$D$ には $v$ 以下の情報が全て揃ったので、$v$ に対するクエリに答える。
    • $v$ 以下の探索が完了したので、親に戻る。この時、
      • $v$ が親にとっての Heavy-child なら、そのまま親に戻る。
      • $v$ が親にとっての Light-child なら、$D$ を初期化してから戻る。
        • 初期化は、再び部分木を全走査(☆)して1つずつ $D$ から取り除いてもいいし、もし高速に初期化できるならさらなる計算量改善になる。

(☆)の部分は、同じ部分木に対して複数回、登録・削除がおこなわれる可能性がある。 ここでも定数倍の高速化を行う手法として、最初にオイラーツアーを1回行って訪問順に頂点を列挙しておけば、 任意の部分木はその中で連続区間で表されるので、通常のDFSより高速に部分木の頂点を全走査できる。

DSU on Tree は、通常の木の構造に加え、以下を決めれば実装できる。

  • get_ans(v, D)
    • 現在の $D$ から頂点 $v$ に関する答えを求める
  • add(v, D)
    • $D$ に頂点 $v$ の情報を登録する
  • reset(D) または remove(v, D)
    • 一括で $D$ を初期化できるなら reset(D)
    • 一括初期化ができないなら、DFSで1頂点ずつ削除する remove(v,D)

今回の場合、状態管理に必要な $D$ は、数個の配列・int で済む。

  • count[c]: 色 $c$ の個数
  • freq[k]: その色の頂点数が $k$ であるような色の個数
  • max_freq: count の最大値

このテクニックをライブラリ化していれば、問題依存の実装部分は少ない。

Python3

programming_algorithm/contest_history/atcoder/2026/0418_abc454.txt · 最終更新: by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0