オムロンプログラミングコンテスト2025 #2(AtCoder Beginner Contest 432)D,E,F,G問題メモ

オムロンプログラミングコンテスト2025 #2(AtCoder Beginner Contest 432)

AC人数が $E \gt D \gt G \gt F$ となる不思議な回。
使うテクニック的には $D,E,F,G$ の順であることは頷けるが、実装の難しさや、典型度合いで逆転が発生した模様。

D - Suddenly, A Tempest

問題文

  • 無限に広がる二次元グリッドがあります。
  • マス $(x,y)$ の色は、$0 \leq x \leq X-1$ かつ $0 \leq y \leq Y-1$ ならば黒であり、そうでなければ白です。
  • このグリッドに対して $N$ 個の大嵐が順に発生します。
  • $i$ 回目の大嵐は、文字 $C_i$ と整数 $A_i, B_i$ で表される法則にしたがって、グリッドを更新します。
    • $C_i$ が X の場合、$x=A_i$ を境として、左は下方向、右は上方向を正として $B_i$ ずつずれる
    • $C_i$ が Y の場合、$y=A_i$ を境として、下は左方向、上は右方向を正として $B_i$ ずつずれる
    • (詳細な意味や例は問題ページ参照)
  • $N$ 個の大嵐がすべて発生した後のグリッドについて、黒マスの連結成分の個数と、各連結成分における黒マスの個数を昇順に出力してください。

制約

  • $N, X, Y$ は整数
  • $1 \leq N \leq 14$
  • $1 \leq X,Y \leq 10^8$
  • $C_i$ は X または Y
  • $A_i, B_i$ は整数
  • $-10^8 \leq A_i, B_i \leq 10^8$

解法

ちょっと問題設定の理解に時間がかかる。
嵐より、(災害として生々しいが、理解の上では)地震による縦ずれ・横ずれ断層の方がわかりやすいかもしれない。

嵐の発生回数が $N \le 14$ と少ない。

最初の黒マスの配置は長方形で、嵐によって長方形はタテまたはヨコに切断された形(=結局、長方形)になる。
長方形は、$(左下x,y,右上x,y)$ の4パラメータで表現できるので、この長方形の集合を愚直に更新して全て持てる。

最終的な長方形の個数の最大は、思いつけた中では、野菜を切るときのように 「タテにずらす」→「次にタテにまとめてずらせるよう、ヨコにずらして重ねる」を $N/2=7$ 回繰り返して、$2^7=128$ 個となる。
これ以上があったとしても、1回の切断で長方形は2個にしか分かれないし、毎回全ての長方形を切断はできないので、$2^N$ よりかなり少なくなると見積もれる。

この程度なら、全ての長方形ペアについて互いに隣り合っているか調べることができる。
隣り合っている長方形を Union-Find などで結合していけば、連結成分の個数がわかり、 リーダーに自身の面積を加算していけば、各連結成分の黒マス数も分かる。

$\lt$ か $\le$ かなどの細かな境界に注意が必要(1WA)

Python3

E - Clamp

問題文

  • 長さ $N$ の整数列 $A=(A_1, A_2, \dots, A_N)$ が与えられます。
  • $Q$ 個のクエリが与えられるので、順に処理してください。
    • 1 x y : $A_x$ の値を $y$ に変更する。
    • 2 l r : $\displaystyle \sum_{1\leq i\leq N} \max(l, \min(r, A_i))$ の値を求める。

制約

  • $1\leq N \leq 5\times 10^5$
  • $1\leq Q \leq 2\times 10^5$
  • $0\leq A_i \leq 5\times 10^5$
  • $1$ 種類目のクエリについて、
    • $1\leq x\leq N$
    • $0\leq y \leq 5\times 10^5$
  • $2$ 種類目のクエリについて、
    • $0\leq l,r \leq 5\times 10^5$
  • 入力は全て整数

解法

なんか本番で全然ピンとこなかった。
きっと最近、SegmentTreeBeatsを履修してて引っ張られたのが悪い。

クエリ2では、$r$ とのmin→$l$ とのmax の順に処理するため、$l \ge r$ なら答えは明らかに $l \times N$ である。

そうで無い場合、各クエリ時点の以下の情報があれば答えを計算できる。

  • $l$ 未満の $A_i$ の個数
  • $r$ より大きい $A_i$ の個数
  • $l$ 以上 $r$ 以下の $A_i$ の総和

2本の FenwickTree によって $A_i$ の個数と総和をそれぞれ管理すればよい。

Python3

F - Candy Redistribution

問題文

  • $N$ 人の子供がいます。子供たちには $1$ から $N$ までの番号が付けられています。
  • 子供 $i$ は飴を $A_i$ 個持っています。
  • あなたの目標は、以下の操作をできるだけ少ない回数実行し、最終的に $N$ 人の子供全員が同数の飴を持っている状態にすることです。
    • 相異なる $2$ 人の子供 $x,y$ を選び、子供 $x$ が持っている飴の個数以下の正の整数 $z$ を選ぶ。
    • 子供 $x$ が持っている飴のうち $z$ 個を回収し、子供 $y$ に渡す。
  • 最終的に $N$ 人の子供全員が同数の飴を持っている状態になる操作列が存在するか判定してください。存在する場合は、そのうち操作回数が最小になるようなものを $1$ つ求めてください。

制約

  • $2 \leq N \leq 20$
  • $1 \leq A_i \leq 10^8$
  • 入力される値はすべて整数

解法

$A_i$ の総和が $N$ の倍数で無ければ不可能。倍数の場合は、回数はともかく可能である。以下、可能とする。

最終的な1人当たりの飴の個数を $D=\frac{\sum A}{N}$ とする。
以下、$A_i$ を $A_i-D$ に置き換える。

この時、$A_i=0$ になったものは明らかに操作の必要が無いので除いておき、$N$ もそれに合わせて更新する。
(アルゴリズム上はやらなくても問題ないが、考える上で紛れを減らすため)
最後に復元の必要があるので、元のindexが分かる情報は持っておく。

$A_i$ を正と負のグループに分け、両グループから1個ずつ取り出していく。
$+a$ と $-b$ を取り出したとき、$a=b$ なら2人を一度の操作で“解決”できる(その子たちの持つ飴が $D$ 個になる)が、 そうでない場合はどちらか1人のみが解決し、1人は次の相手と続けて操作する必要がある。

全て1人ずつ“解決”した場合、$N-1$ 回(最後のみはかならず同時に解決できる)かかるが、 途中で $a=b$ となった回数だけ操作回数を減らすことができる。 というわけで、なるべく「$a=b$ となるような回数が多い」ように操作したい。
これは、以下のように考えられる。

  • 「和が $0$ となる集合」を「良い集合」とする。
  • 良い集合内の子どもの間で操作をおこなっていけば、その集合内を全て解決するタイミングで1グループに1回 $a=b$ となるタイミングが訪れる。
  • →$A$ を、なるべく多くの「良い集合」に分割する方法を見つければよい。

これを求めるには以下のbitDPが思いつくが、

  • $\mathrm{DP}[S]:=$ 集合 $S$ で作れる良い集合の最大数

以下のように $S$ の部分集合 $T$ を全て試すやり方だと $O(3^N)$ かかってしまうのでTLEとなる。

  • TLE解法
    • $S$ の全ての部分集合 $T$ について、
    • $T$ が良い集合である場合、$\mathrm{DP}[S]←\mathrm{DP}[S \backslash T]+1$ でmax更新する

ところで集合 $S$ はその中での良い集合の作り方によらず総和は決まっており、 例えば総和 $-x$ であるところに要素 $x$ を加えると、良い集合は必ず $1$ 増え、それ以外では増えない。
よって、「$S$ に含まれるどの $x$ を最後に加えたとするか」を試すと、$O(N2^N)$ で求めることができる。

  • $g$ を、$S$ の総和が $0$ なら $1$、そうでないなら $0$ の値を取る変数として、
  • $S$ に含まれる各要素 $x$ について、
  • $\mathrm{DP}[S]←\mathrm{DP}[S \backslash \{x\}]+g$ で max更新する

このDPをバックトラックできるように進めていくと、それを実現するグループ分けを復元できる。

バックトラックを考慮すると、DPの意味合いを少し変えて「$S$ を最大の良い集合に分割した上で、余る要素があったらそれも $1$ 個に数える」にすると楽。
これなら、バックトラックしていって「$\mathrm{DP}[S]$ が同じである間」は同じグループであることを意味するようになる。

嘘解法

$DP$ の更新時、上述の通り $O(3^N)$ かけて $S$ の部分集合 $T$ について全て試すのはTLEだが、 「どれでもいいので、$\mathrm{DP}[T]=1$ であるような部分集合 $T$ があれば、$S$ から貪欲に取っていい」 という山勘で実装したら、通ってしまった。

反例は以下のような場合、

A: +1 +2 -3 -1 -2 +3

最も良い集合が多くなるのは $(+1,-1),(+2,-2),(+3,-3)$ だが、 $(+1,+2,-3),(-1,-2,+3)$ のように分割してしまう可能性がある。

Python3

G - Sum of Binom(A, B)

問題文

  • 長さ $N$ の正整数列 $A=(A_1,A_2,\dots,A_N)$ および長さ $M$ の正整数列 $B=(B_1,B_2,\dots,B_M)$ が与えられます。
  • $\displaystyle \sum_{i=1}^{N} \sum_{j=1}^{M} \binom{A_i}{B_j}$ の値を $998244353$ で割った余りを求めてください。
  • ここで、$\displaystyle \binom{x}{y}$ は $x$ 個のものの中から $y$ 個のものを選ぶ場合の数(二項係数)を表し、特に $x \lt y$ のときは $0$ です。

制約

  • $1\leq N,M \leq 5\times 10^5$
  • $1\leq A_i,B_j \leq 5\times 10^5$
  • 入力は全て整数

解法

畳み込みを使って二項係数の和をまとめて計算するテクニックとして、以下がある。

  • $\displaystyle \sum_{i=0}^{n} \sum_{j=0}^{m} \frac{(i+j)!}{i!j!}$

これは、

  • $\displaystyle F=(\frac{1}{0!},\frac{1}{1!},\frac{1}{2!},...,\frac{1}{n!})$
  • $\displaystyle G=(\frac{1}{0!},\frac{1}{1!},\frac{1}{2!},...,\frac{1}{m!})$

これを畳み込んで $H=F*G$ とすると、$H_k$ は $k=i+j$ になるような $(i,j)$ の $\dfrac{1}{i!j!}$ の総和となるので、 各 $H_k$ に $k!$ をかけていくと、計算できる。

これを少し応用すると今回の問題が解ける。求めるものは以下のように表せるので、

  • $\displaystyle \sum_{i=1}^{N} \sum_{j=1}^{M} \frac{A_i!}{B_j!(A_i-B_j)!}$($A_i \ge B_j$ であるもののみ)

indexを負の方にも拡張して

  • $F_i=$「$A_k=i$ となるような $k$ の個数」×$i!$
  • $G_{-j}=$「$B_k=j$ となるような $k$ の個数」×$\dfrac{1}{j!}$

これを畳み込むと $H_k$ は「$A_i-B_j = k$ となる全 $(A_i,B_j)$ の $\dfrac{A_i!}{B_j!}$ の総和」となる。
$k \lt 0$ となるものは考慮しないので、$k \ge 0$ の結果のみ、$\dfrac{1}{k!}$ をかけて総和を取ると答えとなる。

実際には、$G$ の方のindexにはオフセットを加えて正としてから計算するとよい。

Python3

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