2019年10月8日火曜日

ARC 059 E - キャンディーとN人の子供

ARC 059 E - キャンディーとN人の子供

部分点のケースについて考える。xix_iが一定の場合、
g(k,y):=a1+...+aN=yi=1kxiaig(k, y) := \sum_{a_1 + ... + a_N = y} \prod_{i=1}^k x_i^{a_i}

とすると答えはg(N,C)g(N, C)で、漸化式は

g(k,y)=g(k1,y)+xkg(k1,y1)+...+xkyg(k1,0)g(k, y) = g(k-1, y) + x_k g(k-1, y-1) + ... + x_k^y g(k-1, 0)

g(0,0)=1g(0, 0) = 1

g(0,y)=0(y0)g(0, y) = 0 \qquad (y \neq 0)

と書ける。DPすれば計算量はO(NC2){\mathcal O}(NC^2)

元の制約についてもだいたい同じように解ける。

h(k,y):=x1=A1B1x2=A2B2...xN=ANBNa1+...+aN=yi=1kxiaih(k, y) := \sum_{x_1 = A_1}^{B_1} \sum_{x_2 = A_2}^{B_2} ... \sum_{x_N = A_N}^{B_N} \sum_{a_1 + ... + a_N = y} \prod_{i=1}^k x_i^{a_i}

とするとやはり答えはh(N,C)h(N, C)で、

h(k,y):=a1+...+aN=yx1=A1B1x2=A2B2...xN=ANBNi=1kxiai:=a1+...+aN=y(x1=A1B1x1a1)(x2=A2B2x2a2)...(xN=ANBNxNaN)\begin{aligned} h(k, y) & := \sum_{a_1 + ... + a_N = y} \sum_{x_1 = A_1}^{B_1} \sum_{x_2 = A_2}^{B_2} ... \sum_{x_N = A_N}^{B_N} \prod_{i=1}^k x_i^{a_i} \\ & := \sum_{a_1 + ... + a_N = y} \Bigl( \sum_{x_1 = A_1}^{B_1} x_1^{a_1} \Bigr) \Bigl( \sum_{x_2 = A_2}^{B_2}x_2^{a_2} \Bigr) ... \Bigl( \sum_{x_N = A_N}^{B_N}x_N^{a_N} \Bigr) \end{aligned}

だから、漸化式は

h(k,y)=h(k1,y)+(xk=AkBkxk)h(k1,y1)+...+(xk=AkBkxky)h(k1,0)h(k, y) = h(k-1, y) + \Bigl( \sum_{x_k = A_k}^{B_k} x_k \Bigr) h(k-1, y-1) + ... + \Bigl( \sum_{x_k = A_k}^{B_k} x_k^y \Bigr) h(k-1, 0)

となって、やはりDPすれば計算量はO(NC2){\mathcal O}(NC^2)

いろはちゃんコンテスト Day2 E - 連呼

いろはちゃんコンテスト Day2 E - 連呼

AAAが登場しない列を数えて(N+M2N)\binom{N+M-2}{N}から引く方針が良さそう。

とりあえず、最初の文字と最後の文字の制限がないとして考えてみる。AAAが登場しない列とは、AAAMM個のBの間に挿入してできる列である。Add個にわけるとして、Axx個、AAyy個になったとすると、

x+2y=Nx + 2y = N

x+y=dx+y = d

より、y=Nd,x=2dNy = N-d, x = 2d-Nと整理できる。AAAの並べ方は(dy)\binom{d}{y }通りあり、MM個のBへの挿入の仕方は(両端を含めると)(M+1d)\binom{M+1}{d}通りある。したがって、総和はd(M+1d)(dy)\sum_d \binom{M+1}{d}\binom{d}{y}となる。(有効なddについてだけ計算する。)

さて、最初の文字がAで最後の文字がBという制限を満たすものを数える。最初がABから始まるパターンとAAから始まるパターンにわけて数えることにする。

最初がABから始まる場合、最後がBなので、AB·B·...·B·で示したM1M-1箇所から選んでAAAを挿入することになる。総和はd(M1d)(dy)\sum_d \binom{M-1}{d} \binom{d}{y}である。ただし、x+2y=N1,x+y=dx+2y = N-1, x+y =d だからy=N1dy = N-1-dである。

最初がAAから始まる場合、AAB·B·...·B·で示したM1M-1箇所から選んでAAAを挿入することになる。総和はやはりd(M1d)(dy)\sum_d \binom{M-1}{d} \binom{d}{y}である。ただし、x+2y=N2,x+y=dx+2y = N-2, x+y=d だからy=N2dy=N-2-dである。

2019年9月29日日曜日

ABC 142 E - Get Everything

ABC 142 E - Get Everything

NNが小さいので、dp[S]=dp[S] = 集合SSに含まれる宝箱を全部開けられるような鍵の選び方をした時の最小コスト、を目指したくなる。(不可能な場合は\infty。)

O(2NM){\mathcal O}(2^NM)のDP

iiで開けられる宝箱の集合をCiC_iとするとき、dp[]:=0dp[\empty] := 0と初期化して

dp[SCi]:=min(dp[SCi],dp[S]+Ci)dp[S \cup C_i] := \min (dp[S \cup C_i], dp[S]+C_i)

をすべての鍵と集合について更新して求まるなら話は早い……が、これは集合の包含関係について全部処理しているわけではない。つまり、この方法で最終的に求まるのはdp[S]=dp[S] = 集合SSに含まれる宝箱を全部開けられて、SSに含まれない宝箱を一つも開けられないような鍵の選び方をした時の最小コスト、である。しかし、もう少し考えると、求めるのは全体集合に対する最小コストだったので、それでも問題ないことがわかる。1

コンテスト中はこの辺の理解にぱっと至らなくて、しばらく逡巡してしまった。

O(3N+M){\mathcal O}(3^N + M)のDP

最初に目指したdpdpを直接求める方法を考えてみる。dpdpをすべて\inftyに初期化して、

dp[]:=0dp[\emptyset] := 0

dp[Ci]:=aidp[C_i] := a_i

とする。(重複する場合は小さい方を選ぶ。つまり、正確にはdp[Ci]:=min(dp[Ci],ai)dp[C_i] := \min (dp[C_i], a_i)。)dpdpを上位集合に関してゼータ変換すると、dp[S]=dp[S] = 宝箱の集合SSを一つの鍵で開ける場合の最小コスト、になっている。あとは、

dp[S]:=minTS(dp[T]+dp[ST])dp[S] := \min_{T \subseteq S}(dp[T]+dp[S \setminus T])

を使って順次最小値を確定していけばよい。部分集合の効率的な列挙のやり方は https://topcoder.g.hatena.ne.jp/jackpersel/20100804/1281196966 に書いてある。


  1. 何個か宝箱の集合クエリを受け取ってそれに対する最小コストを返す問題の場合は、最後にdpdpをゼータ変換すればよい。 ↩︎

2019年9月26日木曜日

ARC 100 E - Or Plus Max

ARC 100 E - Or Plus Max

以下、ビット列とそれが表す集合を同一視する。インデックスの集合S,T{0,1,...,2N1},S,T2S, T \subseteq \{0, 1, ..., 2^N-1\}, |S|, |T| \le 2が与えられたとき、STS \oplus Tを、インデックスiSTi \in S \cup Tの中からAiA_iが大きいもの2つを残した集合とする。\oplusは結合的で可換なのでゼータ変換が適用できる。つまり、

F(S):=TSf(T)F(S) := \oplus_{T \subseteq S} f(T)

という変換ができる。fff(S):={S}f(S) := \{S\}でよい。

これでijKi \lor j \subseteq Kを満たすi,ji, jに対するAi+AjA_i + A_jの最大値が求まるが、実際にはijKi \lor j \le Kを満たすi,ji, jについて考えなくてはならない。ijKk{0,1,...,K}i \lor j \le K \Leftrightarrow k \in \{0, 1, ..., K\}が存在してij=kk{0,1,...,K}i \lor j = k \Leftrightarrow k \in \{0, 1, ..., K\}が存在してijki \lor j \subseteq kだから、ijKi \lor j \le Kを満たすi,ji, jに関する最大値はmaxkK(F(k)+F(k))\max_{k \le K} (F(k)の片方 + F(k)のもう片方)として累積的に求まる。


高速ゼータ変換に関するメモ。高速ゼータ変換は各部分集合SUS \subseteq Uの各要素をちょうど一回ずつ重複なく処理するので、集合の合併を取る場合はそのままくっつけて良い。つまり、例えばg(S):=Sg(S) := Sという関数を和集合についてゼータ変換したいとするとき、つまり

G(S):=TSg(T)G(S) := \bigcup_{T \subseteq S} g(T)

を求めるとき、和集合S1S2S_1 \cup S_2を得る操作で重複を除く必要はない。


Common Lispに関するメモ。扱う集合が小さいので珍しくunionを使う気になったら初歩的な失敗をした。非破壊的な合併を書いているつもりでなんとなく(sort (union list1 list2) ...)としたのだけど、unionはリストをコピーするとは限らないのでsortで元のリストが破壊される。(sort (nunion (copy-list list1) (copy-list2 list2)) ...)とするか、あるいはunionしてからcopy-listするのが正しかった。

非破壊的操作はコピーするとは限らないというポイント、わかっているつもりで普段は忘れている。(sort (mapcar ...) ...)みたいなのも(mapcar #'identity list)listをそのまま返すような最適化は許されているので、元のリストが保たれるとは限らない。(SBCLでは大丈夫だけど。)本質的には危険なコードをけっこう書いてしまっている気がする。


SBCLに関するメモ。SBCLのmergeはいちいちsb-kernel:specifier-typeを呼ぶので1回の呼び出しが重い。使う時には注意する必要がある。