Sky株式会社プログラミングコンテスト2023(AtCoder Beginner Contest 329)E,G 問題メモ

E - Stamp

問題

  • 長さ $N$ の英大文字からなる文字列 $S$ と、長さ $M$ の英大文字からなる文字列 $T$
  • 長さ $N$ の文字列 $X=$ '###…#' に、以下の操作を好きなだけ繰り返して、$S$ にすることができるか判定せよ
    • $X$ の連続する長さ $M$ の好きな位置を、$T$ で置き換える
  • $1 \le N \le 2 \times 10^5$
  • $1 \le M \le 5$

解法

$M$ が小さいので、「$S$ の $i$ 文字目と合わせることが可能な $T$ のindexの集合」を持って、$S$ の左から状態を管理する。

   12345
T: ABABC

S: A B A B A B C C A B C
   1 2 3 4
       1 2 3 4 5
           1 2
                 5
                   1 2
                   3 4 5

$S$ の $i$ 文字目と合わせられる $T$ のindexの集合を $D_i$ とする。(たとえば $D_3=\{1,3\}, D_4=\{2,4\}$ など)

$D_{i-1}$ がわかっているとき、$D_i$ は、以下を合わせたものとなる。

  • 1つ前から継続する
    • $D_{i-1}$ に含まれる要素($k$ とする)について、$S_i = T_{k+1}$ なら、$k+1$
  • $i-1$ より左を作った後、$i$ にスタンプの左端を合わせて押す
    • $S_i=T_1$ なら $1$
  • $i$ より右を作った後、$i-1$ にスタンプの右端を合わせて押す
    • $M$ が $D_{i-1}$ に含まれる場合のみ、$S_i = T_j$ であるような任意の $j$

で、途中で $D_i$ がなくなったらアウト。

なくならずに最後までいって、$M$ が $D_N$ に含まれていれば成功となる。

Python3

G - Delivery on Tree

問題

  • $N$ 頂点の根付き二分木があり、根である頂点1に空のカゴがある
  • $M$ 個のボールがある。それぞれ頂点 $S_i$ に現在位置し、$T_i$ に届けたい
  • カゴにボールを積み下ろしすることで、全てのボールを届けたい頂点に持っていきたい
  • 各頂点では以下の3つができる
    • カゴを隣接する頂点に移動させる
    • $S_i$ でボール $i$ をカゴに積む
    • $T_i$ でボール $i$ をカゴから下ろす
      • (つまり、$S_i$ でも $T_i$ でもない頂点でボール $i$ を積み下ろしはできない)
  • カゴに一度に積めるボールは $K$ 個まで
  • 各辺を1往復ずつする軌跡(オイラーツアー)のうち、全てのボールを届けたい頂点に持っていくことが可能な軌跡の個数を $\mod{998244353}$ で求めよ
  • $2 \le N \le 10^4$
  • $1 \le M \le 2 \times 10^5$
  • $1 \le K \le 10^3$
  • 各頂点、子は最大2個まで

解法

比較的素直な解法だが、素直にやれば解けるという確信に至るまでの丁寧な考察と、実装力が問われる。

可能不可能の判定

まず、可能不可能を判定する。

  • LCA(最小共通祖先)を挟んで両側に $(S_i,T_i)$ があったら、$S_i$ を先に訪れる必要がある。
  C     こんな(S,T)があったら、Cからは左を先に訪れる必要がある
  /\
 S○
   |
   T

これがボール間で矛盾すると答えは“0”。

矛盾せず、子が2個ある各頂点につき「左/右を先に訪れなければならない」「どちらでもよい」が決まったとする。

この「どちらでもよい」でどうするかが唯一の自由度となるが、 その頂点を訪れたときに既にカゴに積まれていたボールの個数によっては、どちらか/両方が不可能な場合もある。

個数ごとに場合分けが必要そう。(制約も $NK=10^7$ といい感じだし)

各頂点ですべき操作は決まる

ある頂点でカゴにボールを積み下ろしする最適な順番は、無駄なボールを積まないことを意識するとある程度決まる。
仮に、先に訪れる子を左の子とする。

  • ①カゴが親から $v$ に着く
  • ②親方面に $S_i$ があって、$T_i=v$ のボールがあったら(積んできてるはずなので)下ろす
  • ③左の部分木に $T_i$ があって、$S_i=v$ のボールを積む
  • ④左の子に行く
  • ⑤…(左の子探索中)…
  • ⑥左の子から帰ってくる
  • ⑦左の部分木に $S_i$ があって、$T_i=v$ のボールを下ろす
  • ⑧右の部分木に $T_i$ があって、$S_i=v$ のボールを積む
  • ⑨右の子に行く
  • ⑩…(右の子探索中)…
  • ⑪右の子から帰ってくる
  • ⑫右の部分木に $S_i$ があって、$T_i=v$ のボールを下ろす
  • ⑬親方面に $T_i$ があって、$S_i=v$ のボールを積む
  • ⑭親方面に帰る

この操作と異なる手順でボールを積み下ろししても、悪くなることはあってもよくなることはない。
(※悪くなる=$v$ に来た時点でのカゴのボールの個数が同じ状況で、上記の操作では $K$ 個を超えないが、異なる操作では超えてしまう)
(※よくなる=$v$ に来た時点でのカゴのボールの個数が同じ状況で、上記の操作では $K$ 個を超えてしまうが、異なる操作では超えない)

なので、親・左・右別に、$S_i=v,T_i=v$ となる個数、計6通りを数えておくと、操作を追えそう。

子が1個なら①~⑦⑬⑭、子がない葉頂点なら①②⑬⑭だけでよいが、いずれにしろ最適な操作は決まる。

木DP

  • $DP[i,j]=$ 頂点 $i$ に、カゴの中のボール $j$ 個で親から訪れたときに、取れるパターン数

$i,j$ に対し、①~⑭の操作をたどることで遷移を考える。
先ほどの操作の番号を、そのまま積み下ろしするボールの個数として扱うと、

  • $DP[v,k]=DP[l,k-②+③] \times DP[r,k-②+③+⑤-⑦+⑧]$ … (A)

となる。ただし、$l,r$ は $v$ の左右の子、⑤は「左に行って帰ってきた際に変動するボールの個数(左の部分木内で積まれた数-下ろされた数)」とする。
⑤や⑩は、$S_i$ に+1、$T_i$ に-1をして、葉から根に向かって累積和を取っていくことで各頂点計算できる。

各操作の途中で $0$ 個を下回ったり、$K$ 個を超えるような $k$ は無効なので、その場合は $DP[v,k]=0$ となる。
②,③,⑤,⑦,⑧,…,⑬ でカゴのボールの個数が変化するごとに逐一個数チェックし、途中一度でも超えるとアウト。

上記の遷移(A)は、子が1個の場合は $DP[l,k-②+③]$ のみとなる。
また、どちらを先に訪れても良い頂点なら、右頂点を先に訪れた場合も同様に計算して足し合わせる。

このように木DPをおこない、最終的に $DP[1,0]$ が答えとなる。$O(NK)$

Python3

programming_algorithm/contest_history/atcoder/2023/1118_abc329.txt · 最終更新: 2023/12/05 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0