Processing math: 64%

UNICORNプログラミングコンテスト2022(AtCoder Beginner Contest 269) G,Ex問題メモ

G - Reversible Cards 2

問題

  • N 枚のカードがあり、それぞれ表に Ai、裏に Bi の整数が書かれている
  • Ni=1(Ai+Bi)=M として、k=0,1,2,...,M について、以下の答えを求めよ
    • はじめ、全て表が上になっている
    • 上になっている面に書かれた整数の和をちょうど k にするために、裏返す必要がある枚数の最小値を求めよ
    • どうやっても不可能なら -1 を出力せよ
  • 1N2×105
  • 1M2×105
  • 0Ai,BiM

解法

S=Ai とする。これが初期状態で上になっている面に書かれた整数の和である。
Di=BiAi とする。カード i を裏返すと、これだけ総和が増減する。

以下のナップサック(負の価値がありえるver.)をすると答えが求まるが、O(NM) のためTLEとなる。

  • DP()[i,j]=i 枚目のカードまで考慮して、総和を j にするために裏返す必要のある枚数の最小値

高速化したい。ここで、以下の性質を利用できる。

  • Di には、同じ値がいっぱい含まれやすい
  • Di が同じものは、なるべくまとめて遷移する

Di の「種類数」をなるべく多くするようなテストケースを考えると、 (Ai,Bi)=(0,1),(1,0),(0,2),(2,0),(0,3),(3,0),... のようなものがある。
(※(0,0)はいくらでも増やせるし、意味が無いので省略)

このとき Di=1,1,2,2,3,3,... であり、Ai+Bi=|Di|i(Ai+Bi)=M2×105 である。
制約内で、|Di|=t となるところまで作れるとして、Mt(t+1)2×2 となる。
t<M であり、種類数は O(M) となる。

次に、Di が同じものをまとめて遷移させる方法を考える。Di=d となる ixd 個あったとする。

同じ品物が複数個あるナップサック問題のテクニックとして、2の冪乗のグループを作るというものがある。
xd=1+2+4+8+...+2k+ と分解し、それぞれの個数分をまとめた logxd 個のグループに分ける。

すると、1xd のどのような値もいくつかのグループの組合せで実現でき、遷移回数を O(logxd) まで落とせる。

こうすることで計算量は、単純な見積もりで O(MMlogM) となる。
実際には「M の種類数」と「Di が同じものの個数の多さ」はトレードオフの関係にあるので、より少なくなる。

ちゃんとした計算量見積もりは公式Editorial参照。O(N+M1.5) となるらしい。

Python3

Ex - Antichain

問題

  • N 頂点の、頂点1を根とした根付き木が与えられる
  • k=1,2,...,N について、以下の条件を満たす頂点集合 S の個数を \mod{998244353} で求めよ
    • |S|=k
    • S のどの2つの頂点 u,v も、祖先-子孫関係にない
  • 2 \le N \le 2 \times 10^5
  • 実行制限時間: 8sec.

解法

素朴な木DP(TLE)

以下の木DPを定義し、

  • DP_{(仮)}[v,k]= 頂点 v 以下で、条件を満たすサイズ k の頂点集合の個数

ある頂点から見て異なる子に属する頂点同士は、互いに影響しないので、それぞれ独立に自由に選べる。
よって子のDPの結果をFFTを使って合成していく、という解法が思いつくが、ムカデグラフのような場合に上手くいかない。

  ○
 /\         ← ③合成後サイズ N のFFT
○  ○
   /\       ← ②合成後サイズ N-2 のFFT
  ○  ○
     /\     ← ①合成後サイズ N-4 のFFT
    ○  ○
       /\    (丸付き数字は処理順)
       :  :

既にサイズが肥大化した配列を何度もFFTすることになってしまうので、O(N^2) となる。

こういう時、DPの計算が結合法則を満たすなら、長いpathを分割統治できるHL分解が有効になる場合がある。

HL分解

ある1つの Heavy-path を考える。各頂点にくっつくHeavy-path以外の頂点を、Light-children とする。

○--○--○--○--○--○--○--○    ←Heavy path
 |   |  |\   |   |   |   |
 ○  ○ ○○ ○  :   :   :        ←Light children
  `○    `○

以下を定義する。

  • DP_1[l,r,k]
    • Heavy-path上の区間 [l,r] 上の頂点の、Light-children以下の部分森からの、条件を満たす頂点集合の選び方の個数
  • DP_2[l,r,k]
    • Heavy-path上の区間 [l,r] 上の頂点の、Light-children以下の部分森および [l,r] 上の頂点からの選び方の個数
    • ただし [l,r] 上の頂点は必ず選ばれている
l           r
◎--◎--◎--◎--○--○--○--○  DP1[l,r]=●からの選び方
 |   |  |\   |   |   |   |      DP2[l,r]=●および◎からの選び方、ただし◎からは必ず選ぶ
 ●  ● ●● ●  ○  ○  ○
  `●    `●

こうすると結合法則が成り立ち、Heavy path上のDPを半分ずつ統合していくことができる。

l1          r1  l2          r2
◎--◎--◎--◎--○--○--○--○
 |   |   |   |   |   |   |
 :   :   :   :   :   :   :

DP1[l1,r2] = DP1[l1,r1] * DP1[l2,r2]
DP2[l1,r2] = DP1[l1,r1] * DP2[l2,r2] + DP2[l1,r1]

※ただし * は添字 k に対する畳み込み

Heavy-path の最左頂点を L、最右頂点を R として、DP[L] = DP1[L,R]+DP2[L,R] が、Heavy-path全体の結果となる。

この結果を L の親に伝えれば、親の Heavy-path のDPを計算するのに必要な情報となる。

            P
○--○--○--○--○--○                 親のHeavy-path
 |   |   |  ↑↖
 :   :   :  │ ○--○--○--○--○--○  Light-child1のHeavy-path
            │ L1
            ○--○--○--○--○--○     Light-child2のHeavy-path
            L2

DP1[P,P] = DP[L1] * DP[L2] * ...(子の数だけ畳み込み)
DP2[P,P] = [0, 1]

DP1の畳み込みも、考えなくやると冒頭のムカデグラフと同様の問題が発生しうる。
要素数が小さい方から優先的におこなっていくことで、全体で O(N \log{N}) となる。

Heavy-path上の畳み込みも、隣り合った箇所のサイズが小さい方から上手くやると O(N \log{N}) になるらしいが、 順番を崩さないようにする必要があり難しいので、特に工夫せず2個ずつ統合していく実装でも、O(N \log^2{N}) となる。

実行制限時間が長いので、O(N \log^2{N}) でも間に合う。

Python3

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