2020年1月31日金曜日

yukicoder No.979 Longest Divisor Sequence

一見して端からDPすれば良さそうに見える。つまり、dp[x]:=\operatorname{dp}[x] :=位置xxから始まる最長の部分列の長さ、としてdp[N1]=1\operatorname{dp}[N-1]=1から逆順に埋めていく方法が考えらえるが、単純にdp[x]1+max{dp[y]:xyかつx<yかつAx<Ay}\operatorname{dp}[x] \leftarrow 1+\max \{\operatorname{dp}[y] : x|yかつx < yかつA_x < A_y\}と遷移すると間に合わない。

約数の数があまり多くないことを利用すると遷移の数を減らせそう。つまり、Ay(<Ax)A_y (< A_x)AxA_xの約数であるような$y( dp[y]max(dp[y],1+dp[x]) \operatorname{dp}[y] \leftarrow \max (\operatorname{dp}[y], 1 + \operatorname{dp}[x])

と遷移したい。ただ、これでは同一の約数がたくさん存在するときに遷移先の候補が減っていない。同一の約数についてはいちばん右のものだけ見ればよいから、

stack[k]:=数列(Ai)中に登場する整数kの位置を降順に保持したスタック \operatorname{stack}[k] := 数列(A_i)中に登場する整数kの位置を降順に保持したスタック

を最初に作っておいて、適当にポップしながらスタックの先頭にだけ遷移すればよい。

yukicoder No.980 Fibonacci Convolution Hard

Cq:=a1aq1+a2aq2+...+aq1a1C_q := a_1a_{q-1} + a_2a_{q-2} + ... +a_{q-1}a_1とすると、漸化式は次のように書ける:

Cq=a1aq1+a2aq2+...+aq2a2+aq1a1=a1(paq2+aq3)+a2(paq3+aq4)+...aq2(pa1+a0)+aq1a1=p(a1aq2+...+aq2a1)+(a1aq3+...+aq3a1)+aq2=pCq1+Cq2+aq2 \begin{aligned} C_q & = a_1a_{q-1} + a_2a_{q-2} + ... + a_{q-2}a_2 +a_{q-1}a_1 \\ & = a_1(pa_{q-2} + a_{q-3}) + a_2(pa_{q-3} + a_{q-4}) + ... a_{q-2}(pa_1 + a_0)+ a_{q-1}a_1 \\ & = p(a_1a_{q-2} + ... + a_{q-2}a_1) + (a_1a_{q-3}+...+a_{q-3}a_1) + a_{q-2} \\ & = pC_{q-1}+C_{q-2} + a_{q-2} \end{aligned}

ただしa0=1a_0 = 1とする。

2020年1月29日水曜日

JOI2007 春合宿 - lines

同一の直線は最初に除いておく。以下、NN本の直線は相異なるとする。

平面走査的に考えてみる。つまり、十分に大きな正数aaについてxx軸に平行な直線L:y=aL: y=aを用意して、この直線を下に移動しながら領域の数を数える。y=ay=aと平行な直線はないとしてよい。[1] 最初に直線LLNN本の直線と交わっていて、これらに区切られたN+1N+1個の領域にまたがっている。LLを下に移動していくとき、LLが新たな領域に出会うのは直線の交点に一致した瞬間である。このとき、交点を通る直線が2本なら領域が1つ増え、3本なら2つ増え、一般にkk本の直線の交点ではk1k-1本増えている。

以上の観察より、単にl1l_1から順に直線を追加していき、新しい直線を追加するたびにこれまで追加した各直線と交わる点を(平行でなければ)検出し、同じ交点を検出した回数を数えていけばよい。

NN本の直線を処理したあと、ある交点PPをちょうど22本の直線が通ったなら、PP11回数えられ、33本なら1+2=31+2 = 3回数えられる。一般に、k+1k+1本の直線の交点は1+2+...+k=k(k+1)/21+2+...+k = k(k+1)/2回数えられていて、そこで増えた領域はkk個になっている。二次方程式を解くと、点PPvv回数えられたならそこで増えた領域は(1+1+8v)/2(-1+\sqrt{1+8v})/2個である。計算量はO(N2){O}(N^2)


  1. あったら、全体を適当に回転させたものを考えればよい。 ↩︎

2020年1月26日日曜日

Code Festival 2014 - eject

f(x):=xf(x) :=x回目でONになっている確率とすると、漸化式は

f(x)=kx1k回目でOFFで、あとはずっとONである確率=kx1(1f(k))p(1p)xk1 \begin{aligned} f(x) & = \sum_k^{x-1} k\text{回目でOFFで、あとはずっとONである確率} \\ & = \sum_k^{x-1}(1-f(k))p(1-p)^{x-k-1} \end{aligned}

このままでは間に合わないので差分を計算すると、f(x)f(x1)=p(12f(x1))f(x)-f(x-1) = p(1-2f(x-1))になっていて、f(x)=p+(12p)f(x1)f(x) = p + (1-2p)f(x-1)という線形漸化式が従う。f(x)1/2=(12p)(f(x1)1/2)f(x)-1/2 = (1-2p)(f(x-1) -1/2)より、f(x)=1/2+(12p)x(f(0)1/2)=1/2+(12p)x(1/2)f(x) = 1/2 + (1-2p)^x(f(0)-1/2) = 1/2 + (1-2p)^x\cdot(-1/2)と表せて、これなら計算できる形になっている。

……と迂遠な導出をしてしまったが、よく見ると自明にf(x)=p(1f(x1))+(1p)f(x1)f(x) = p(1-f(x-1)) + (1-p)f(x-1)だった。直前から求まるかどうかは常に最初に考えるべき。


p=1p=1の場合に計算誤差が問題になるようだった。(expt -1d0 奇数)は-1になってほしいが、指数が大きいと1になる。手元だと境界はこの辺:

> (expt -1d0 9007199254740991)
-1.0d0
> (expt -1d0 9007199254740993)
1.0d0

前者(=2531=2^{53}-1)がdouble-floatで正確に表現できる最大の整数なので、そこを超えると精度が落ちるのは明らかとして、そこまではOKなのかは実装次第だが、たぶん普通はOK。

2020年1月25日土曜日

CodeChef Aarambh 2020 - Odd Topic

出現する整数が高々4000種類しかないので、4000個のcompact bit vectorを持ってすべてのクエリに対して累積和で計算する、みたいなムーブを最初にやってしまった。(部分点は取れる。) 結局、バケット法+bitset高速化をした。

簡単のために、数列aabbの長さは両方NNとする。aabbを長さBB毎に区切ってk:=N/Bk := N/B個の区間に分解し、さらにそれぞれの区間に対して長さ40004000のビット配列を保持する。このビット配列をα1,...,αk,β1,...,βk\alpha_1, ..., \alpha_k, \beta_1, ..., \beta_kとする。それぞれの区間について、出現する整数の奇遇を数えて

αi[x]:={1(整数xが区間内に奇数個ある場合)0(整数xが区間内に偶数個ある場合) \alpha_i[x] := \begin{cases} 1 \qquad (整数xが区間内に奇数個ある場合) \\ 0 \qquad (整数xが区間内に偶数個ある場合) \end{cases}

としておく。(βi\beta_iも同様)
各クエリの指定区間に含まれるバケットαi,βi\alpha_i, \beta_iのXORを取って、端の部分は愚直に数えれば、立っているビットの数が答えになる。出現する値の最大値をAAとすると、計算量はO(Q((N/B)(A/logA)+B)){O}(Q((N/B)\cdot(A/\log A) + B))B=Θ(NA/logA)B= \Theta(\sqrt{NA/\log A})ならO(QNA/logA){O}(Q\sqrt{NA/\log A)}。(/logA/ \log Aの部分はいわゆるbitset高速化)

コンテスト中は深く考えずにB=200B=200としたのだけど、計算量を見るとN\sqrt Nよりは大きいほうがよさそうだったのでB=1000B=1000としてみたら遅くなった。なにか勘違いしてるかも。


tmwilliamlinさんの提出を見たら、単純なbitsetの累積和で処理していた。それはそうだった…… O((N+Q)A/logA){\mathcal O}((N+Q)A/ \log A)

Egorさんは出現する整数のほうを下位5ビットごとに分割して、bitsetなしで通している。

2020年1月24日金曜日

ARC 070 D - No Need

xx枚目まで見たときに総和がyy以上になるようなカードの集合の総数をdp[x][y]dp[x][y]とすると、漸化式は

dp[x][y]=dp[x1][y]+dp[x1][yax] \operatorname{dp}[x][y] = \operatorname{dp}[x-1][y] + \operatorname{dp}[x-1][y-a_{x}]

となる。ここで、NN番目のカードとii番目のカードを入れ替えて同じDPをしたものをdpi\operatorname{dp}_iとする。dpi\operatorname{dp}_iについても同じ漸化式が成立し

dpi[N][y]=dpi[N1][y]+dpi[N1][yai] \operatorname{dp}_i[N][y] = \operatorname{dp}_i[N-1][y] + \operatorname{dp}_i[N-1][y-a_i]

順番を入れ替えてもNN枚処理すれば同じ結果になるはずだからdp[N][y]=dpi[N][y]\operatorname{dp}[N][y] = \operatorname{dp}_i[N][y]が成り立つ。したがって

dp[N][y]=dpi[N1][y]+dpi[N1][yai] \operatorname{dp}[N][y] = \operatorname{dp}_i[N-1][y] + \operatorname{dp}_i[N-1][y-a_i]

整理して

dpi[N1][y]=dp[N][y]dpi[N1][yai] \operatorname{dp}_i[N-1][y] = \operatorname{dp}[N][y] - \operatorname{dp}_i[N-1][y-a_i]

という漸化式が立つ。ii番目のカードを除いたとき和がyy以上になるような集合の総数をf[i][y]f[i][y]とするとf[i][y]=dpi[N1][y]f[i][y] = \operatorname{dp}_i[N-1][y]だから

f[i][y]=dp[N][y]f[i][yai] f[i][y] = \operatorname{dp}[N][y] - f[i][y-a_i]

dp\operatorname{dp}を求めた後、さらにffについてDPできる。

カードiiが不必要
\Leftrightarrow カードiiを含む和がKK以上になるような集合と、カードiiを含まない和がKK以上になるような集合が一対一対応する
f[i][K]=dp[N][K]f[i][K]\Leftrightarrow f[i][K] = \operatorname{dp}[N][K] - f[i][K]

だから、この等式が成立するようなiiを数えればよい。

dp\operatorname{dp}の値は64ビットに収まらないので、適当にmodをとって扱う。弱衝突耐性があれば十分なので32ビットでよさそう。だめなら複数modを組み合わせればよい。


戻すDPを覚えるために復習。

参考: https://sen-comp.hatenablog.com/entry/2019/11/06/121325

ARC 028 D - 注文の多い高橋商店

とりあえず「簡単すぎる」ほうの問題を解く。dp[x][y]:=\mathrm{dp}[x][y] :=商品を最初のxx種類からちょうどyy個選ぶ方法の総数、とする。dp[x][y]=d[x1][y]+dp[x1][y1]+...+dp[x1][yax]\mathrm{dp}[x][y] = d[x-1][y] + \mathrm{dp}[x-1][y-1] + ... + \mathrm{dp}[x-1][y-a_{x}]と遷移できるが、これでは間に合わないので累積和を考える。つまりDP[x][y]:=\mathrm{DP}[x][y] :=商品を最初のxx種類からyy以下選ぶ方法の総数としたとき、

DP[x][y]=dp[x][0]+dp[x][1]+...+dp[x][y] \mathrm{DP}[x][y] = \mathrm{dp}[x][0]+ \mathrm{dp}[x][1] + ... + \mathrm{dp}[x][y] DP[x][y]=DP[x][y1]+dp[x][y] \mathrm{DP}[x][y] = \mathrm{DP}[x][y-1] + \mathrm{dp}[x][y] dp[x][y]=DP[x1][y]DP[x1][yax1] \mathrm{dp}[x][y] = \mathrm{DP}[x-1][y] - \mathrm{DP}[x-1][y-a_x-1]

が成り立つので、

DP[x][y]=DP[x][y1]+DP[x1][y]DP[x1][yax1] \mathrm{DP}[x][y] = \mathrm{DP}[x][y-1] + \mathrm{DP}[x-1][y] - \mathrm{DP}[x-1][y-a_x-1]

というO(1){O}(1)の遷移を導ける。ここからyyを一個ずらした漸化式DP[x][y1]=DP[x][y2]+DP[x1][y1]DP[x1][yax2]\mathrm{DP}[x][y-1] = \mathrm{DP}[x][y-2] + \mathrm{DP}[x-1][y-1] - \mathrm{DP}[x-1][y-a_x-2]を引くと、dp\mathrm{dp}についても同じ形の漸化式が得られる:

dp[x][y]=dp[x][y1]+dp[x1][y]dp[x1][yax1] \mathrm{dp}[x][y] = \mathrm{dp}[x][y-1] + \mathrm{dp}[x-1][y] - \mathrm{dp}[x-1][y-a_x-1]

これで簡単なほうの問題が解けた。

さて、ここでNN番目の商品とii番目の商品を入れ替えて同じDPをすることを考える。この配列をdpi\mathrm{dp}_iとするとき、dpi\mathrm{dp}_idp\mathrm{dp}と同じ遷移で求まり、しかもNN種類の商品を処理すると同じ値になるはずである。つまり、任意の個数yyについてdp[N][y]=dpi[N][y]\mathrm{dp}[N][y] = \mathrm{dp}_i[N][y]が成り立つ。したがって

dpi[N][y]=dpi[N][y1]+dpi[N1][y]dpi[N1][yai1] \mathrm{dp}_i[N][y] = \mathrm{dp}_i[N][y-1] + \mathrm{dp}_i[N-1][y] - \mathrm{dp}_i[N-1][y-a_i-1]

という等式から

dp[N][y]=dp[N][y1]+dpi[N1][y]dpi[N1][yai1] \mathrm{dp}[N][y] = \mathrm{dp}[N][y-1] + \mathrm{dp}_i[N-1][y] - \mathrm{dp}_i[N-1][y-a_i-1]

が導け、整理すると

dpi[N1][y]=dp[N][y]dp[N][y1]+dpi[N1][yai1] \mathrm{dp}_i[N-1][y] = \mathrm{dp}[N][y] - \mathrm{dp}[N][y-1] + \mathrm{dp}_i[N-1][y-a_i-1]

となる。ii番目の商品を除いた場合に全体でちょうどyy個の商品を選ぶ方法の総数をf[i][y]f[i][y]とするとき、f[i][y]=dpi[N1][y]f[i][y] = \mathrm{dp}_i[N-1][y]であるから、

f[i][y]=dp[N][y]dp[N][y1]+f[i][yai1] f[i][y] = \mathrm{dp}[N][y] - \mathrm{dp}[N][y-1] + f[i][y-a_i-1]

となり、ffについてDPすればクエリにO(1){O}(1)で答えられることになる。O(NM+Q){O}(NM + Q)


戻すDPというらしい。

参考:
http://sigma425.hatenablog.com/entry/2015/07/31/121439

2020年1月20日月曜日

EDPC X - Tower

問題文と制約を見てO(Nmaxsi){O}(N \max s_i)で解くしかないという気持ちになる。順番が固定されていないが、固定されていないと難しすぎるのでその方法を探す。

丈夫さについても重さについても、大きいブロックは下に置いたほうが良いはずなので、まずはsi+wis_i+w_iで降順にソートするという案を思いつく。実際、それでよいことが証明できる。有効なタワーが与えられたとし、丈夫さと重さが下のブロックから(s1,w1),(s2,w2),...,(sk,wk)(s'_1, w'_1), (s'_2, w'_2),..., (s'_k, w'_k)になっているとする。隣り合うブロックでsi+wisi+1+wi+1s'_i + w'_i \le s'_{i+1} + w'_{i+1}が成り立つペアがあったとき、ブロックiiとブロックi+1i+1を入れ替えても有効なタワーのままであることが示せれば十分である。

ブロックiii+1i+1を入れ替えたとき、1,...,i11, ..., i-1番目とi+2,...,ki+2, ..., k番目のブロックにかかる重さは変わらず、ii番目だったブロックにかかる重さは減るので、i+1i+1番目だったブロックについてだけ考えればよい。このブロックに以前掛かっていた重さをGGとすると、新しい配置で掛かる重さはG+wiG+w'_iになる。ところでii番目だったブロックは丈夫さsis'_iで重さG+wi+1G+w'_{i+1}に耐えていたのでsiwi+1Gs'_i - w'_{i+1} \ge Gが成り立つ。si+wisi+1+wi+1s'_i + w'_i \le s'_{i+1} + w'_{i+1}からsi+1wisiwi+1Gs'_{i+1} - w'_i \ge s'_i - w'_{i+1} \ge Gが従い、丈夫さsi+1s'_{i+1}G+wiG+w'_iに耐えられることがわかる。従って、ブロックiii+1i+1を入れ替えてもタワーは有効である。\square

si+wis_i+w_iの降順に並んだタワーだけ考えればよいことがわかったので、あとは端からDPすればよい。dp[x][y]:=\operatorname{dp}[x][y] := ブロック1,2,...,x1, 2, ..., xから作れて、耐えられる重さがyyであるようなタワーの価値の最大値、とする。

dp[x+1][min(ywx+1,sx+1)]max(,dp[x][y]+vx+1) \operatorname{dp}[x+1][\min(y-w_{x+1}, s_{x+1})] \leftarrow \max (*, \operatorname{dp}[x][y] + v_{x+1}) dp[x+1][y]max(,dp[x][y]) \operatorname{dp}[x+1][y] \leftarrow \max(*, \operatorname{dp}[x][y])

と遷移すればよい。(*は左辺)

2020年1月16日木曜日

CODE FESTIVAL 2016 Relay H - 早起き

T=0T=0の場合に高橋君が起きる時刻をc1,c2,...,cNc_1, c_2, ..., c_Nとする。数列(ci)(c_i)全体に定数を足して(または引いて)、mod  86400\mod 86400{0,1,...,10800}\{ 0, 1, ..., 10800\}に含まれるようなcic_iの数を最大化すればよい。ところで、(ci)(c_i)の代わりに後者に定数を足すと考えてもよい。つまり、cic_imod  86400\mod 86400で分類した時に、[x,x+10800][x, x+10800]に含まれるcic_iの数を(xxを動かして)最大化する問題と考えられる。調べる範囲はx=0,1,...,86399x=0, 1, ..., 86399でよく、累積和で処理しておけば各ステップはO(1){O}(1)で済む。

2020年1月15日水曜日

第一回 アルゴリズム実技検定

リアルタイム受験した。3時間58分で全完。普段のABCよりさらに典型寄りという感じがした。

F

ソートするだけなのだが、計算量がすぐにはわからなかった。例えば単語がkk個あるとしてヒープソートすると、新しい単語wiw_iをヒープに挿入するたびに最悪O(logk){O}(\log k)回の単語比較が必要で、比較にかかる時間は自身の長さで抑えられるから全体でO((w1+...+wk)logk)O(NlogN){O}((|w_1| + ... + |w_k|)\log k) \subseteq {O}(N\log N)とわかる。最悪計算量がO(NlogN){O}(N \log N)であるような比較ソート一般でもそうなるとは思うけど、よくわからない。

H

平衡二分木でシミュレーションできるのでコンテスト中はそうした。

コンテストが終わったあと考えていたが、奇数番目カードの最小値、全体の最小値、奇数番目カードのオフセット、全体のオフセットの4つを保持すれば配列だけでシミュレーションできる。

I

ABC 142 - Eとだいたい同じ問題。ゼータ変換から部分集合に関するDP(O(3N+M){O}(3^N+M))をやろうとして、これは普通のbitDP (O(2NM){O}(2^NM))で解けることを思い出した。

J

任意の始点から与えられた3点に向かう最小費用流を求めるイメージが浮かんだが、それはなさそう。→そもそも、フローが流れる辺が重複した時にコストが2回数えられるのはまずいのでは→いや、その時は別の始点から重複なしで流したのと同じ形になっているので、最小値には影響しないはず→だったら各点から3点への最短距離を求めれば十分では?→全点からダイクストラ。

よく考えると、全点からダイクストラの代わりに3点からダイクストラで十分だった。

N

l,rl, rについて、[lC,l][l-C, l]あるいは[r,r+C][r, r+C]を空ける場合のコストを求めて比較すればよい。累積和でできるけど、これも遅延伝搬付き平衡二分木で楽をした。

O

f(x):=f(x) :=xxを出したあとの操作回数の期待値、として漸化式を書き出したあと、どう更新するか迷った。xxが最大のときf(x)=0f(x) = 0で、xxが小さくなるにつれて有効な項が単調に増えていくので、降順に更新していくのがよさそう。特にデータ構造は必要ないのだが、なぜかBITが必要だと思ってしまった。


問題が公開されたのでもう一回解きなおしたら、全体的にかなり手間取った。当日は運がよかっただけかもしれない。

2020年1月14日火曜日

CodeChef January Challenge 2020 - Chefina and Prefix Suffix Sums

0+sufN=pre1+sufN1=pre2+sufN2=...=preN+00+suf_N = pre_1 + suf_{N-1} = pre_2+suf_{N-2} = ... = pre_N + 0であることに注目する。この等式より、pren=sufnpre_n = suf_n入力全体の和/(N+1)入力全体の和/(N+1)と一致しなければならない。この数が入力の中に2つなければ答えは00である。

さて、この2数を除いた2(N1)2(N-1)個の数の中から残りを埋めることになる。まず、ai+bi=Sa_i + b_i = Sになるような数の組み合わせを入力から探す。これらを

{a1,b1},{a2,b2},...,{ak,bk} \{a_1, b_1\}, \{a_2, b_2\}, ..., \{a_k, b_k\}

とするとき、すべてのppについてapa_pbpb_pは入力の中に必ず同数存在しなければならない。逆に同数あれば、prei=appre_i = a_pのときsufNi=bpsuf_{N-i} = b_pprei=bppre_i = b_pのときsufNi=apsuf_{N-i} = a_p)のように埋めていけば有効な列ができることになる。

{a1,b1},{a2,b2},...,{ak,bk}\{a_1, b_1\}, \{a_2, b_2\}, ..., \{a_k, b_k\}がそれぞれc1,...,ckc_1, ..., c_kペアあるとわかったら、あとは{a1,b1}\{a_1, b_1\}から順にN1N-1個の位置を埋めていけばよい。つまり、

(N1c1)2c1(N1c1c2)2c2...(N1c1...ck1ck)2ck \binom{N-1}{c_1}2^{c_1} \cdot \binom{N-1-c_1}{c_2}2^{c_2} \cdot ... \cdot \binom{N-1-c_1-...-c_{k-1}}{c_k}2^{c_k}

が答えになる。N1N-1箇所の空きからc1c_1個選んで、さらにa1a_1b1b_1のどちらを置くか選んで…… というイメージ。ai=bia_i = b_iのケースは22掛けがないので注意すること。


冒頭の事実に気づくのになんと二日かかってしまった。

CodeChef January Challenge 2020 - English

まず、接頭辞と接尾辞を両方考慮するという設定は、接頭辞だけの設定に直せる。アルファベットが26×2626 \times 26種類あることにして、単語とその反転を合わせて扱えばよい。例えばpoemなら(p,m)(o,e)(e,o)(m,p)(p, m)(o, e)(e, o)(m, p)という列だと思えばよい。

さて、本題は重みつきマッチングだが、一般のマッチングアルゴリズムでは無理なのでgreedyを探す。まず思いつくのはスコアの高いペアからマッチングしていくことで、実はこれでよいことがわかる:

単語x,yx, yの最長共通接頭辞の長さをf(x,y)f(x,y)とする。また、4つの単語a,b,c,da, b, c, dが与えられたとし、ffが最大になる組み合わせが{a,b}\{a, b\}だとする。このとき、接頭辞の性質により

  • f(c,d)min(f(c,a),f(a,b),f(b,d))f(c, d) \ge \min(f(c, a), f(a, b), f(b, d))

が成り立ち、f(c,d)f(c,a),f(c,d)f(a,b),f(c,d)f(b,d)f(c, d) \ge f(c, a), f(c, d) \ge f(a, b), f(c, d) \ge f(b, d)のどれを仮定してもf(a,b)2+f(c,d)2f(a,c)2+f(b,d)2f(a, b)^2 + f(c, d)^2 \ge f(a, c)^2 + f(b, d)^2が成り立つので{a,b},{c,d}\{a, b\}, \{c, d\}と組み合わせるのがもっともスコアが高くなる。

したがって、マッチングとそれに含まれる2つのペア{u,v},{x,y}\{u, v\}, \{x, y\}が与えられた時、4点の中でもっともスコアが高くなる辺を優先してマッチングをつなぎかえても全体のスコアは下がらない。このことより、最大スコアを達成するマッチングは上述のgreedyで得られることが示された。\square

実装をどうするかで迷ったが、Trieにすべての単語を登録した上で、DFSして深いものから順にマッチングを決めていった。

2020年1月11日土曜日

第6回 ドワンゴからの挑戦状 予選 B - Fusing Slimes

求めるのは(N1)!(N-1)!通りの移動に関する距離の総和である。

f(p,q):=xpf(p, q) := x_pからxqx_qへの移動による距離の総和、とする。この移動が起こるのは、スライムp+1,...,q1{p+1}, ..., {q-1}が自由な順番で選ばれ、そのあとでppが選ばれ、さらにそのあとでqqが選ばれるケースである。この選び方を数えると、N1N-1箇所の中からqp+1q-p+1箇所を選び、その中の始めのqp1q-p-1箇所にスライムp+1,...,q1p+1, ..., q-1を自由な順番で配置し、末尾の2箇所にスライムp,qp, qを配置し、残ったN1(qp+1)N-1-(q-p+1)箇所に残りのスライムを自由に並べればよい。つまり、

f(p,q)=(N1qp+1)(qp1)!(N1(qp+1))!×(xqxp)=(N1)!(qp+1)(qp)(xqxp) \begin{aligned} f(p, q) & = \binom{N-1}{q-p+1}(q-p-1)!(N-1-(q-p+1))! \times (x_q-x_p)\\ & = \frac{(N-1)!}{(q-p+1)(q-p)}(x_q-x_p) \end{aligned}

ただし、q=Nq = Nの場合はスライムqqが選ばれないので

f(p,q)=(N1Np)(Np1)!(N1(Np))!×(xNxp)=(N1)!(qp)(xNxp) \begin{aligned} f(p, q) & = \binom{N-1}{N-p}(N-p-1)!(N-1-(N-p))! \times (x_N - x_p) \\ & =\frac{(N-1)!}{(q-p)}(x_N - x_p) \end{aligned}

となる。ffの総和を単純に計算すれば部分点が取れる。

上の式でxqxpx_q-x_pに掛かる係数は幅qpq-pのみによって決まっているので、同じ幅について(qNq \neq Nの場合に関して)整理してみる:

p=1N1q=pN1f(p,q)/(N1)!=121(x2x1)+...+121(xN1xN2)+132(x3x1)+...+132(xN1xN3)+..+1(N1)(N2)(xN1x1)=121((x2+x3+...+xN1)(x1+x2+...+xN2))+132((x3+x4+...+xN1)(x1+x2+...+xN3))+...+1(N1)(N2)(xN1x1) \begin{aligned} & \sum_{p=1}^{N-1}\sum_{q=p}^{N-1}f(p, q)/(N-1)! \\ = &\frac{1}{2\cdot 1}(x_2-x_1) + ... + \frac{1}{2\cdot 1}(x_{N-1}- x_{N-2})\\ & + \frac{1}{3\cdot 2}(x_3-x_1) + ... + \frac{1}{3\cdot 2}(x_{N-1}- x_{N-3}) \\ & + .. \\ & + \frac{1}{(N-1)\cdot (N-2)}(x_{N-1}-x_1) \\ = &\frac{1}{2\cdot 1} \bigl( (x_2+x_3 + ... + x_{N-1}) - (x_{1}+x_2 + ... +x_{N-2}) \bigr )\\ & + \frac{1}{3\cdot 2} \bigl( (x_3+x_4 + ... + x_{N-1}) - (x_1 + x_2 + ... + x_{N-3}) \bigr) \\ & + ... \\ & + \frac{1}{(N-1) \cdot (N-2)}(x_{N-1} - x_1) \\ \end{aligned}

(N1)(N-1)!は共通なので除いて整理した。) これは累積和で処理できる形になっている。


満点に達するのに3時間かかった。解説の方針のほうが確かにずっと明快だけれど、上の整理くらいはさっさとできるようになりたい。

2020年1月9日木曜日

いろはちゃんコンテスト Day 2 J - ヌクレオチド

NNが偶数の場合を考える。左右のN/2N/2文字をそれぞれL,RL, RとするとRRLLの反転になる。つまり、LLの中で転倒Ai>Aj,i<jA_i > A_j, i < jが起こっているとき、RRの対応する位置ではAi<AjA_{i'} < A_{j'}となる。L,RL,Rをあわせると、Ai=AjA_i = A_jであるi,ji, j以外は1回ずつ転倒を数えていいことになる。つまり、LL00zz個含まれるとすると、Ai=Aj=0A_i = A_j = 0なる組が(z2)\binom{z}{2}個、Ai=Aj=1A_i = A_j = 1なる組が(N/2z2)\binom{N/2-z}{2}個含まれるので、LLの並べ方全体からこれらを引けば(LL内とRR内の)転倒の総数が(N/2z)(z2)(N/2z2)\binom{N/2}{z}-\binom{z}{2}-\binom{N/2-z}{2}と数えられる。あとはLL内の11RR内の00による転倒を足して

(N/2z)(z2)(N/2z2)+(n/2z)z=K \binom{N/2}{z}-\binom{z}{2}-\binom{N/2-z}{2} + (n/2-z)z = K

LL00zz個含まれるときにはこの等式が成立していなければならないことになる。式を整理すると2z2Nz+K=02z^2 -Nz + K = 0となって、この二次方程式の整数解は高々2つなので、2つの解について数列の総数を求めればよい: (N/2z1)+(N/2z2)\binom{N/2}{z_1} + \binom{N/2}{z_2}

NNが奇数の場合についても、例えば中央が00と決めて同様の議論をすればよい。(対称性により、中央が11の場合も同数の列がある。)

2020年1月1日水曜日

JOI2014 春合宿 - 歴史の研究

オフラインなのでMo's algorithmが適用できる。つまり、平衡二分木(あるいはセグメント木+座標圧縮)などを用意して、区間[l,r][l, r][l,r+1][l, r+1]に伸ばすときはdp[Xr+1]+=Xr+1dp[X_{r+1}] += X_{r+1}と更新して、[l,r][l, r][l,r1][l, r-1]に縮めるときはdp[Xr]=Xrdp[X_r] -= X_rとすればよい。[l,r][l, r]から[l1,r][l-1, r][l,r][l, r]から[l+1,r][l+1, r]なども同様である。それぞれのクエリについてmaxdp\max dpが答えになっている。O(NQlogN){O}(N \sqrt{Q} \log N)


平衡二分木を使ったらTLEしてしばらく放置していたが、よく考えると全体のmax\maxにしか興味がないので(優先度の変更可能な)優先度付きキューを使えば十分なのを思い出した。えびちゃんさんの記事にやり方が載っているので、書いたら通った。

CADDi 2018 E - Negative Doubling

完成した数列はどこかで符号がマイナスからプラスに切り替わることに注目する。N=5N=5なら、

  • ++++++++++
  • ++++-++++
  • +++--+++
  • ++---++
  • +----+
  • -----

の6通りがありえる。たとえばA3A_3で符号が切り替わると決めてしまうと、あとはA3,A4,A5A_3, A_4, A_5の好きな数に好きなだけ4を掛けて単調非減少にして、A2,A1A_2, A_1にも4を掛けて単調非減少にして、最後にA2,A1A_2, A_12-2を掛ければ全体として単調非減少になっている。

そこで、f+(x):=Ax,Ax+1,...,ANf^+(x) := A_x, A_{x+1}, ..., A_{N}を単調非減少にするための最小コスト、と定める。おそらくf+(N+1)=f+(N)=0f^+(N+1) = f^+(N) = 0からスタートしてx=N1,N2,..x=N-1, N-2, ..と順にf+(x)f^+(x)の値を求めていけそう。さらに、f(x):=Ax,Ax1,...,A1f^-(x) := A_{x}, A_{x-1},..., A_{1}を単調非減少ににするための最小コスト、とすれば(f(x)+x)+f+(x+1)(f^-(x) + x) + f^+(x+1)の最小値が答えになる。(+x+x2-2を掛けるぶんのコスト。)

さて、f+f^+を求める具体的な方法が問題になる。この手順が与えられれば、ff^-は同じ方法を逆順の配列に適用するだけでよい。先に述べた通り、f+(N+1)=0f^+(N+1)=0からスタートして降りていくことにする。とりあえず、正しくないがシンプルな方針から:

  1. AxAx+1A_x \le A_{x+1}ならf(x):=f(x+1)f(x) := f(x+1)
  2. Ax>Ax+1A_x > A_{x+1}ならAx+1A_{x+1}に何回か44を掛けて非減少になるようにする。つまり、Ax4kAx+1A_x \le 4^k A_{x+1}となるような最小のkkを求めて、以降のAx+2,...,ANA_{x+2}, ..., A_{N}にも同じだけ44を掛ける。このとき、f(x):=f(x+1)+2k(Nx)f(x) := f(x+1) + 2k(N-x)となる。

この方法の問題点は入力例1に適用してみればわかる:

4
3 1 4 1

f(5)=0,f(4)=0,f(3)=2,f(2)=2f(5) = 0, f(4) = 0, f(3) = 2, f(2) = 2までは正しく求まるが、A1=3A_1=3を処理する際に44を掛ける必要があるのはA2=1A_2=1だけで、A3,A4A_3, A_4はそのままでいいことを見落としていた。つまり、A2,A3A_2, A_3の間にはすでに44倍の差があるので、一回分の44掛けに限ってはA2A_2で吸収されてしまうことになる。一般に、Ax+1A_{x+1}AxA_x4k4^k倍以上である時、kk回ぶんの44掛けはAxA_xに吸収されてAx+1A_{x+1}以降には影響しない。このkkxx容量 と呼ぶことにする。上の手順で降りていくとき、各AxA_xについて(容量が正なら)(x,容量)(x, 容量)のペアをスタックに積んでいくことにする。

  1. AxAx+1A_x \le A_{x+1}ならf(x):=f(x+1)f(x) := f(x+1)xxの容量が正ならスタックに(x,容量)(x, 容量)を積む。
  2. Ax>Ax+1A_x > A_{x+1}ならAx+1A_{x+1}に何回か44を掛けて非減少になるようにする。つまり、Ax4kAx+1A_x \le 4^k A_{x+1}となるような最小のkkを求める。スタックの先頭が(y,容量)(y, 容量)になっているとき、Ax+1,...,AyA_{x+1}, ..., A_{y}min(容量,k)\min (容量, k)だけ44を掛ける。このとき、操作回数は2min(k,容量)(yx)2\min(k, 容量)(y-x)だけ増えるが、容量がkkより小さい場合はさらにスタックをポップしながらkk回の44掛けが終わるまで繰り返す。

各インデックスについて、スタックへのpushとpopは1回しか行われないので、スタック操作は全体でO(N){O}(N)になっている。