AtCoder Beginner Contest 252 G,Ex問題メモ

G - Pre-Order

問題

  • $1~N$ の順列 $P=(P_1,P_2,...,P_N)$ が与えられる
  • $N$ 頂点の根付き木のうち、以下の条件を満たすものの個数を $\mod{998244353}$ で求めよ
    • 各頂点に $1~N$ の番号がついていて、根は頂点 $1$
    • 根からDFSで訪れた頂点番号を行きがけ順に記録すると $P$ になる
    • 1つのノードが複数の子を持つ場合は、必ず頂点番号が小さい順に訪れる
  • $2 \le N \le 500$
  • $P_1=1$

※行きがけ順: はじめて訪れたときに1回だけ記録する

解法

区間DP。

  • $DP1[l][r]=P_l,P_{l+1},...,P_{r-1}$ の $[l,r)$ の区間で同じことを考えて、1つの部分木は何通りあるか?
  • $DP2[l][r]=P_l,P_{l+1},...,P_{r-1}$ の $[l,r)$ の区間で、木が複数本あってもよい場合は何通りあるか?
P[l,r) = (5 3 7 2 4 8)

DP1での管理対象例:

  5          5
 / \        / \
3   7      3   4
   /|\     |   |
  2 4 8    7   8
           |
           2

DP2での管理対象例: 上記に加え、

  5  7        ←ただし根は番号昇順に並んでいること
  |  |
  3  2
    / \
   4   8

$P$ の添字を $P_0,P_1,...,P_{N-1}$ に置き換えて考えるとして、$DP1[0][N]$ が答えとなる。

$DP1[l][r],DP2[l][r]$ を考えるとき、それより短い区間は全て計算済みとして、遷移は

  • $P_l$ を根とした1つの木を作る場合:
    • $[l+1,r)$ からできる複数の木を、$P_l$ 直下にまるっとくっつける
    • $DP1[l][r]=DP2[l+1][r]$
  • 複数の木からなる森を作る場合:
    • 最初の木がどこまで続くか $k$ を全探索
    • $[l,k)$ まで1つの木、$[k,r)$ は複数の木
    • $P_l \lt P_k$ という条件ときのみ、そこで区切ってくっつけられる
    • $\displaystyle DP2[l][r]=\sum_{k=l+1}^{r-1}DP1[l][k] \times DP2[k][r] \ if \ P_l \lt P_k$

これで区間が短い方から計算していける。
初期値は、各 $i=0,...,N-1$ につき $DP1[i][i+1]=DP2[i][i+1]=1$ としておけばよい。

で、このまま解いても別にいいのだが、上記の遷移からもわかるとおり、$DP1[l][r]$ は $DP2[l+1][r]$ と同じとなる。
従って、実際に管理するのは $DP2$ のみでよく、$DP2[1][N]$ が答えとなる。

Python3

Ex - K-th beautiful Necklace

問題

  • $N$ 個の宝石、色は $C$ 種類
    • 宝石 $i$ の色は $D_i$、価値は $V_i$
    • どの色の宝石も、少なくとも1個は存在する
  • 各色から宝石を1つずつ選ぶ
    • 選んだ $C$ 個の宝石の $V_i$ の総XORを、その選び方の「美しさ」とする
  • $K$ 番目に美しい選び方をした時の美しさを求めよ(最も美しいのが1番目)
    • 同じ美しさでも、選び方が異なれば別に数える
  • $1 \le C \le N \le 70$
  • $0 \le V_i \le 2^{60}$
  • $1 \le K \le 10^{18}$

解法

計算量解析と、半分全列挙。

計算量から立てられる方針

$N=70$ という、十分小さいが愚直には大きすぎる中途半端な制約。

もし選び方を全探索するとすると、色が $1,2,...$ である宝石の個数を $L_1,L_2,...$ として、 選び方の個数はこれらの総積となる。

しかし、$L_i$ の総和の上限が70である。
最大でもそこまで大きくならないのでは? ということで、上限を考える。

和が一定の場合の総積は、でかい値が1個あるよりは、2とか3とかがいっぱいあった方が、明らかにでかくなる。

具体的には、

  • $1$ は総積を増やすのに全く寄与しないため、無い方がよい
  • $L_i \ge 4$ のとき、$2$ と $L_i-2$ に分割した方がでかくなる(小さくならない)
  • $2$ が3個と $3$ が2個では、$3$ が2個の方が積がでかくなる
  • よって、$L_i$ の総和が一定の時、一番総積がでかくなるのは、$3$ がなるべくいっぱいあるとき

ということになる。
1とか2とかでなく、3という値に意味があるのって、なんか珍しい気がする。

よって、全探索の上限は $2^2 3^{22}=1.25 \times 10^11$ となる。

もちろん、これはまだ多すぎるが、半分こすれば $3.5 \times 10^5$ と、十分可能な量となる。
綺麗に半分こできるとは限らないが、そんなに大きくならないことが期待できる。

半分全列挙の考え方を用いて、 互いに同程度となるように色を2グループに振り分け、各グループだけで作れるXORを全て計算しておく。

$K$ 番目を求める

2つの、要素数 $3.5 \times 10^5$ くらいの多重集合ができた。(グループA,Bとする)
あとは、互いから1つずつ取り出してXORして作れる値から、$K$ 番目に大きいものを求めればよい。
だが、この方法もなかなか難しい。

TLEとなるが、基本的な考え方をまず示す。

両グループの値をそれぞれBinary Trieに登録する。
各ノードに「自身の部分木に含まれる要素数」を持たせておく。

A: (0101, 0111, 1101, 1111)    B: (0001, 0100, 1110)

      ④                   ③
   0/  \1             0/  \1
  ②      ②           ②      ①
   \1      \1        0/  \1     \1
   ②      ②        ①  ①     ①
  0/\1    0/\1     0/   0/       \1
 ①  ①  ①  ①    ①   ①       ①
  \1  \1  \1  \1    \1 0/       0/
  ①  ①  ①  ①    ① ①       ①

答えを二分探索する。
「XORの結果、$X$ より大きい値はいくつあるか?」は、1回につき、2つのTrieの小さい方のノード数程度で抑えられる。

$f($ A側で着目中のノード $a$, B側で着目中のノード $b$, 着目中のbit $d$, 境界値 $X)$ として、 $a,b$ の各ノード以下に登録された値同士のXORで $X$ 以上になるものの個数を返す関数を考える。

$d$ は大きい方から考えるとして、$X$ で $d$ が立っているなら、、、

  • 以下の2つの部分木の組では、XORで $d$ bit目が0になり $X$ 以上にならない
    • $a$ の0の方の部分木と、$b$ の0の方の部分木
    • $a$ の1の方の部分木と、$b$ の1の方の部分木
  • 以下の2つの部分木の組で、再帰的に求める
    • $f(aの0の方の部分木, bの1の方の部分木, d>>1, X)$
    • $f(aの1の方の部分木, bの0の方の部分木, d>>1, X)$

$X$ で $d$ が立っていないなら、、、

  • 以下の2つの部分木の組では、XORで $d$ bit目が1になり必ず $X$ 以上になるため、即座に答えに加算してよい
    • $a$ の0の方の部分木と、$b$ の1の方の部分木
    • $a$ の1の方の部分木と、$b$ の0の方の部分木
  • 以下の2つの部分木の組で、再帰的に求める
    • $f(aの0の方の部分木, bの0の方の部分木, d>>1, X)$
    • $f(aの1の方の部分木, bの1の方の部分木, d>>1, X)$

これで $f($ AのTrieの根, BのTrieの根, 1«59, X)$ とすると、全体での答えが求められる。

$X$ を二分探索し、「$X$ 以上になるXORが $K$ 個以上になる、最も小さい値」が答えとなる。

計算量は、まずBinaryTrieへの登録が、$O(3^{\frac{N}{6}}) \propto 3.5 \times 10^5$ 個の値で $O(\log{V_{max}}) \propto 60$ 個のノードを作るので、だいたい $10^7$ に数倍をかけたくらい。

次に二分探索が、最悪の場合は全ノードが探索されうるので、1回あたり $O(3^{\frac{N}{6}}\log{V_{max}})$ なので、 答えまで行き着くには $O(3^{\frac{N}{6}}\log{V_{max}}\log{K})$ で、$10^8~10^{10}$ くらいとなり、ちょっと厳しい。

まぁ、実際にほぼ全ノードが探索されうるような $X$ はあったとしても限られるので、 全ての二分探索の試行がここまでかかるわけではないが、実際にTLEだった。

小手先の高速化

logを1つにする方法もある(公式Editorial参照)が、小手先の高速化を試みる。

結構、BinaryTrieの構築の時点で、それなりの計算量がかかっている。
最初にグループA,Bの全ての値をTrieに登録しきってしまうことはせず、遅延させることを考える。

ノードに個数で無く、値の多重集合自体を持たせておき、二分探索で必要とされたときに初めて(まだ作ってなければ)子を作る。

二分探索開始前のグループAのTrieは、根にグループAの多重集合だけ持った状態

      (0101,0111,1101,1111)  ←根

二分探索で仮に $X=0110$ などが試されたとして、$f(a,b,d,X)$ による再帰で次の $a,b$ として必要になる箇所だけ、作られてなければその時に作る。

      (0101,0111,1101,1111)
         0/           \1
    (0101, 0111)   (1101, 1111)
          \1
       (0101,0111)
        0/      \1
     (0101,)   (0111,)
                  \1
                 (0111,)

$f(a,b,d,X)$ は、$a,b$ セットで再帰していくので、Bの側のTrieに次の $b$ に該当するノードが存在しなかったりした場合は、 $a$ の側でそれ以下を作っていることが無駄だったりする($X$ が変わったら改めて必要になることはありえるが)ので、 この無駄を省くことで一定の高速化になる。

また、$d$ が深く(下の桁に)なるにつれ、そのノード以下にある値の個数は少なくなり、 よほど人為的に作らない限りは $60$ bit目まで降りなくても早々に1などになる。

$f(a,b,d,X)$ を求めるとき、$|a| \times |b|$ が100~1000など、 全探索できる程度に十分に小さければ、その時点で全探索して、再帰せず直接求めてしまえばよい。

このような高速化を図ることで、2秒ギリギリではあるが、通る。

Python3

programming_algorithm/contest_history/atcoder/2022/0521_abc252.txt · 最終更新: 2022/05/28 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0