2021年2月27日土曜日

キャディプログラミングコンテスト2021 (ABC 193) F - Zebraness

キャディプログラミングコンテスト2021 (ABC 193) F - Zebraness

マスを二つに分類する最適化問題なので、最小カットで解けないかと思ってyosupoさんの記事で解ける条件を確認する。

「Xが赤で、Yが青ならZ円の報酬」
「Xが赤で、Yが赤ならZ円の罰金」
は最大カットになって解けないと思います。

この問題は異なる色に塗ると得になる制約なので、解けそうにない……と思ったが、市松模様の一方の色を反転すると、同じ色なら得になる制約に変換できる。

10410^4頂点なので一般のフローアルゴリズムで解けるのかわからなかったが、とりあえず書くと通った。

Dinicの計算量について
https://misawa.github.io/others/flow/dinic_time_complexity.html

Toph - Subset of Master Shifu

更新がなければwavelet matrixで解ける。更新がある場合、更新が起こる位置が高々6種類しかないことを利用できそう。列BBA1,...,A6A_1, ..., A_6をそれぞれ含む/含まないという分類によって、26=642^6 = 64個の列に分解すると、更新は個々の部分列に対する定数加算として記録できる。質問クエリに対しては、各部分列の[L,R][L, R]に対応する区間について「XX以下の要素は何個あるか?」という問いに答えられれば、その総和がKK以上となるような最小のXXが答えになるので二分探索できる。これはやはりwavelet matrixでできる。

計算量は、A=max(maxiAi,maxiCi),P=maxiPiA= \max(\max_i A_i, \max_i C_i), P=\max_i P_iとして、クエリあたりO((log(AN))22P){O}((\log (AN))^2 2^P)となる。(二分探索とwavelet matrixの処理が共にO(log(AN)){O}(\log (AN))。)かなり重いがQ=104Q=10^4で3秒なのでぎりぎり通る。

editorialを読んだ。ほぼ同じことをやっているようだが、wavelet matrixを64個用意する必要はなくて、1個用意すればほかはその列全体に定数を足したものになっている。確かに。オーダーは変わらないがだいぶ速くなった。

2021年2月20日土曜日

CodeChef - Little Elephant and Movies

制約を見て三乗想定かと思ったら二乗想定だった。

O(N2K){O}(N^2K)

P1P2...PNP_1 \le P_2 \le ... \le P_Nと仮定してよい。また、f(i):=f(i) := 映画iiと同じpriorityを持つ映画の中でもっとも右にあるものの位置、とする。P0=0P_0 = 0としておくと都合がよい。

dp[x][y][z]=x\operatorname{dp}[x][y][z] = x本映画を見た時点で、今までに見た映画で最もpriorityの高い映画の位置(タイは最初に見たものを優先)がyyで、現在のexcitingnessがzであるような場合の数

でDPできる。

遷移について。x<f(y)x < f(y)ならexcitingでない映画がf(y)xf(y)-x個あって

dp[x+1][y][z]+=(f(y)x)dp[x][y][z] \operatorname{dp}[x+1][y][z] \mathrel{{+}{=}} (f(y)-x)\operatorname{dp}[x][y][z]

excitingな映画y=f(y)+1,f(y)+2,...,Ny' = f(y)+1, f(y)+2, ..., Nについては

dp[x+1][y][z+1]+=dp[x][y][z] \operatorname{dp}[x+1][y'][z+1] \mathrel{{+}{=}} \operatorname{dp}[x][y][z]

素朴にやるとO(N3K){O}(N^3K)だが、累積和を取りながら遷移してO(N2K){O}(N^2K)(Pi)(P_i)のsuffixがタイになっている場合に、最後にそれらを全部足す必要があることに注意。

O(NK){O}(NK)

Editorialを見たらO(NK){O}(NK)想定だった。同じpriorityの映画をまとめて、降順に挿入していけばよい。(リンク先参照)

Myers, Wilf. Left-to-right maxima in words and multiset permutations

discussionに載っていた論文。この問題とわずかに違って同じpriorityの映画は区別できない設定だが、考え方は同じで、区別できるようにするには各priorityの頻度の階乗を掛けるだけ。

よく考えるとこれはDEGwerさんの数え上げPDF(3.探索順の変更)に載っている典型とほぼ同じだった。

CodeChef - Expected Greatest Common Divisor

答える数が特殊。y=PQ1y=P \cdot Q^ {-1}mod  109+7\mod 10^9 + 7で求まっているとき、NyN \equiv -yである。

gcd\gcdの期待値を求める問題だが、総和が求まればよい。

GCDごとに求める典型を思い出す。f(X,r):={(x0,...,xN1)X:gcd(x0,...,xN1)=r}f(X, r) := |\{(x_0, ..., x_{N-1}) \in X : \gcd(x_0, ..., x_{N-1}) = r\}|とする時、求める総和はrrf(X,r)\sum_r rf(X, r)と表せる。最大公約数の代わりに公約数を考える: F(X,r):={(x0,...,xN1)X:rx0,...,xN1の公約数}F(X, r) := |\{(x_0, ..., x_{N-1}) \in X : r が x_0, ..., x_{N-1}の公約数\}|rrx0,...,xN1x_0, ..., x_{N-1}の公約数であることはx0,...,xN1x_0, ..., x_{N-1}がすべてrrの倍数であることと同値で、これは成分毎にわけて数えられる。つまり、[Ai,Bi][A_i, B_i]中のrrの倍数の数がBi/r(Ai1)/r\lfloor B_i / r \rfloor - \lfloor (A_i-1) / r \rfloorで、F(i[Ai,Bi],r)=i(Bi/r(Ai1)/r)F(\prod_i [A_i, B_i], r) = \prod_i \bigl( \lfloor B_i / r \rfloor - \lfloor (A_i-1) / r \rfloor \bigr)と計算できる。F(X,r)=rmf(X,m)F(X, r) = \sum_{r|m} f(X, m)なので、ffFFのメビウス変換で得られる。

全体の計算量は、B=maxBiB= \max B_iとしてO(BK+BloglogB){O}(BK + B\log\log B)A/r\lfloor A / r \rfloorが高々O(A){O}(\sqrt A)種類の値しか取らないから、頑張ればO(KB){O}(K \sqrt B)でできる?

ABC162 Eがこれの簡単バージョンだったことを思い出した。

2021年2月14日日曜日

CodeChef - MST queries

最初のMSTに使われなかったいくつかの辺の重みが0になったとする。path optimality conditionを考えると、それらの辺を全域木に加えてできたサイクルから最大重みの辺を除く、を繰り返せば最適になるので、最初の全域木に使われている辺+重み0になった辺だけでMSTを求めても正しい結果が得られる。重みを元に戻した時も、「最初のMSTに使われなかった辺がいくつか0になった状態」であることには変わりないので同じである。したがって、重みが00になっている辺と最初の全域木に使われた辺だけを見て毎回Kruskalなどをするという方針は正しい。

ただ、更新クエリによって重み0の辺が増えていくと、質問クエリで走査する辺の数は結局O(Q+N){O}(Q+N)になってしまうので厳しそうに見える。各時点での重み00の辺によるconnectivityをundo可能Union-Findに保持しておいて(offline dynamic connectivity)、毎回そこからKruskalをする方針だと辺の数はO(N){O}(N)のままなので、全体の計算量はO(Q(logQ+N)logN+MlogM){O}(Q (\log Q + N)\log N + M \log M)にはなりそう。これでもぎりぎりに見えるが…… → 解説を見た。掛かる定数が小さいのでO(Q+N){O}(Q + N)でよいということらしい。

undo付きにするとUnion-Findが遅くなるのでどうかと思ったけれど、一応速くはなるらしい。

2021年2月13日土曜日

CodeChef - Chef and Array

区間の半数を超える同一要素(dominator)があるかどうか判定するクエリ問題。一点更新もある。

方針 1

Editorialの方針。

2つの区間を合併するとき、ある要素が合併後の区間で半数を超えるためには、2つの区間のうちの少なくともいっぽうで半数を超えていなくてはならない、ということを利用できる。

セグメント木の各ノードに、区間内の各要素の出現頻度を保持するマップと(存在すれば)区間のdominatorを保持しておく。質問クエリに対しては、質問の区間がセグ木上のO(logN){O}(\log N)個の区間に分割できるので、O(logN){O}(\log N)個のdominatorの候補を、O(logN){O}(\log N)個のマップに関して調べる。O(NlogN+Qlog2N){O}(N \log N + Q \log^2 N)だが、ハッシュテーブルを使うO(log2N){O}(\log^2 N)なのでかなり重い。

https://www.codechef.com/viewsolution/42694801 1.63 sec.

方針2

n,kn, kを決めておいて、質問クエリの度に区間からnn回無作為に要素を抽出してkk回以上出現した要素についてrange frequency queryを適用してdominatorかどうか調べる。

正答率を評価するには二項分布のCDFを調べればよい。たとえば100回抽出して17回以上出現した要素について調べることにすると、確率1/2で成功する試行を100回繰り返して成功回数が16回以下になる確率が101110^{-11}以下で、10510^5個の質問に対して少なくとも1回間違える確率は10610^{-6}以下と求まる。

https://www.codechef.com/viewsolution/43903194 0.68 sec.

方針 3

rajat1603さんの書き込みより。

区間内でトーナメントのようなことをする。セグメント木の各ノードに(x,残っているxの数)(x, 残っているxの数)を保持することにして、葉(ai,1)(a_i, 1)から

(x1,c1)(x2,c2)={(x1,c1+c2)if x1=x2(x1,c1c2)else if c1c2(x2,c2c1)otherwise (x_1, c_1) \oplus (x_2, c_2) = \begin{cases} (x_1, c_1 + c_2) \qquad \text {if} \ x_1 = x_2 \\ (x_1, c_1 - c_2) \qquad \text {else if}\ c_1 \ge c_2 \\ (x_2, c_2 - c_1) \qquad \text{otherwise} \\ \end{cases}

という演算でマージしていくと(結合法則は成り立たないが)マージの順番や区間の大きさによらず、dominatorは必ず残る。実際に残った要素がその区間でdominatorかどうか判定するためにはrange frequency queryに答えらればよい。O((N+Q)logN){O}((N+Q) \log N)

https://www.codechef.com/viewsolution/42748384 0.22 sec.

方針4

anudeep2011さんの書き込みより。

区間の長さの閾値BBを適当に決めて、長さBB未満の区間は愚直に判定する。

長さBB以上の区間については、その区間のdominatorは与えられた列中にB/2B/2個より多く含まれることを利用できる。具体的には、列中にB/2B/2個より多く存在する数については個別にBIT等を作っておいて、range frequency queryに答えられるようにする。このような数は高々2N/B2N/B種類しかないので、質問に対しては2N/B2N/B個のBITをすべて調べれば答えられることになる。

前処理: O(NlogN(N/B)){O}(N \log N \cdot (N/B)) 質問クエリ(長さBB未満): O(B){O}(B) 質問クエリ(長さBB以上): O((N/B)logN){O}((N/B) \log N) 更新クエリ: O(logN){O}(\log N)

B=NlogNB = \sqrt { N \log N}とするとO((N+Q)NlogN){O}((N+Q) \sqrt {N \log N})

2021年2月12日金曜日

yukicoder No. 1392 - Don't be together

スターリング数のような設定(ボールは区別できる、箱は区別できない、各箱に1つ以上のボールが入る)の数え上げはスターリング数のようなDPができる、という典型を思い出す。

とりあえずスターリング数と同じようにf(x,y):=1,2,...,xf(x, y) := 1, 2, ..., xyy個のグループにわける場合の数、と定める。Px<xP_x < xの場合はスターリング数と同じような遷移[1] f(x,y)=f(x1,y1)+(y1)f(x1,y)f(x, y) = f(x-1, y-1) + (y-1)f(x-1, y)が成立しそうだが、x<Pxx < P_xだとPxP_xの所属するグループに関する未来の情報が必要でうまくいかない。DPするにはもう少し変数を増やす必要がある。

与えられるのは順列なので巡回置換に分解してみる。整数の順序は関係ないので、(1234)(56)(789)(1 2 3 4)(5 6)(7 8 9)のように各巡回置換が連続区間に対応しているものだけ考えればよい。このとき、ffを次のように定義すればDPできる:

f(x,y,z):=1,2,...,xf(x, y, z) := 1, 2, ..., xyy個のグループにわけるとして、xxxxの属する巡回置換の先頭の整数と同じグループに属する(z=1)(z=1)か異なるグループに属する(z=0)(z=0)場合の数。


  1. 遷移の意味付けはdrkenさんの記事を参照。 ↩︎

2021年2月3日水曜日

CODE FESTIVAL 2014 決勝 H - 部屋割り

ii人目が入る部屋の人数は決まっていて、シミュレーションで求まる。ii人目がf[i]f[i]人の部屋に入ったとする。また、

  • dp[i][x]:=i\operatorname{dp}[i][x] := i人が部屋に入った時点でxx人部屋であるような部屋の、最終的な人数の期待値
  • num[i][x]:=i\operatorname{num}[i][x] := i人が部屋に入った時点でxx人部屋であるような部屋の数

とすると、後ろからDPできる:

dp[i][f[i+1]]=dp[i+1][f[i+1]+1]+dp[i+1][f[i+1]]num[i+1][f[i+1]]num[i+1][f[i+1]]+1 \operatorname{dp}[i][f[i+1]] = \frac{\operatorname{dp}[i+1][f[i+1]+1] + \operatorname{dp}[i+1][f[i+1]]\operatorname{num}[i+1][f[i+1]]}{\operatorname{num}[i+1][f[i+1]] + 1}

num[i][f[i+1]]=num[i+1][f[i+1]]+1\operatorname{num}[i][f[i+1]] = \operatorname{num}[i+1][f[i+1]] + 1
num[i][f[i+1]+1]=num[i+1][f[i+1]+1]1\operatorname{num}[i][f[i+1]+1] = \operatorname{num}[i+1][f[i+1]+1] - 1

更新箇所が定数個しかないので、in-placeにDPすればO(N){O}(N)になる。ii人目に関する期待値はdp[i][f[i]+1]\operatorname{dp}[i][f[i]+1]に等しい。