2019年12月25日水曜日

ARC 032 - 仕事計画

辞書式順序の条件を無視すればただの区間スケジューリングなので、始点aia_iでソートして逆から貪欲に選んでいけば仕事の数の最大数kkが求まる。この際に得られた仕事の列が開始から

(a1,b1),(a2b2),...,(ak,bk) (a'_1, b'_1), (a'_2 b'_2), ..., (a'_k, b'_k)

となっているとする。このとき、次の性質があることに注目する:

  • k1k-1個の仕事をこなすためには、少なくともa2a'_2までには始めなければならないし、a2a'_2までに始めればk1k-1個の仕事が可能である
  • k2k-2個の仕事をこなすためには、少なくともa3a'_3までには始めなければならないし、a3a'_3までに始めればk2k-2個の仕事が可能である
  • ...
  • 11個の仕事をこなすためには、少なくともaka'_kまでには始めなければならないし、aka'_kまでに始めれば11個の仕事が可能である

これらの性質から、kk個の仕事からなる辞書順最小のスケジュールを構成する方法が従う:

  • 11個目の仕事はa2a'_2までに終わるものならなんでも可能である。このうちインデックスが最小のものを(l1,r1)(l_1, r_1)とする。
  • 22個目の仕事はr1r_1以降に始まってa3a'_3までに終わるものならなんでも可能である。このうちインデックスが最小のものを(l2,r2)(l_2, r_2)とする。
  • ...
  • kk個めの仕事はrk1r_{k-1}以降に始まるものならなんでも可能である。このうちインデックスが最小のものを(lk,rk)(l_k, r_k)とする。

こうして辞書順最小のスケジュール(l1,r1),...,(lk,rk)(l_1, r_1), ..., (l_k, r_k)が得られた……わけだが、実際にこのスケジュールを求める手順がまだ明らかでない。

時間xxに始まってyyに終わる仕事のインデックスをf(x,y)f(x, y)とするとき(複数ある場合、インデックスが最小のもの以外は捨ててよい)、仕事は二次元空間上の点とみなせるので、上の性質をさらに言い換えられる:

  • [0,a2]×[0,a2][0, a'_2] \times [0, a'_2]上にある仕事についてf(x,y)f(x, y)の最小値を求める。(l1,r1)(l_1, r_1)で最小になるとする。
  • [r1,a3]×[r1,a3][r_1, a'_3] \times [r_1, a'_3]上にある仕事についてf(x,y)f(x, y)の最小値を求める。(l2,r2)(l_2, r_2)で最小になるとする。
  • ...
  • [rk1,+]×[rk1,+][r_{k-1}, +\infty] \times [r_{k-1}, +\infty]上にある仕事についてf(x,y)f(x, y)の最小値を求める。(lk,rk)(l_k, r_k)で最小になるとする。

ここまで整理すると、min\minを乗せたrange treeで実行できる手順になっている。計算量はO(Nlog2N){O}(N \log^2 N)


線形で求まるのは、手順を示されれば確かにとなるけどまったくわからなかった。

今出題されたら、もう少し制約が大きくなってlog2\log^2は落ちる気がする。この方針のままでも、2D range tree + fractional cascading + 構築O(N){O}(N)クエリO(1){O}(1)のRMQでlog\logを1つにできそう? でも、そもそも平面走査的になんとかできないのかな。

2019年12月21日土曜日

CODE FESTIVAL 2015 E - ショートコーディング

CODE FESTIVAL 2015 E - ショートコーディング

次の3つの変形を適用していく:

  • !!!= !!!! = \ !
  • = id-- = \ \operatorname{id}
  • != !!- = \ !

このとき、可能な解は!!,!,id,!!,!,!!, !, \operatorname{id}, -!!, -!, -の6つしかないことがわかる。

2019年12月16日月曜日

WUPC 2019 C - Choose Your Characters

勝てない相手キャラがいないような最短の区間を尺取り法で求める。つまり、dp[i]:=\operatorname{dp}[i] :=キャラiiが勝てるキャラの数、として各区間について正しいdp\operatorname{dp}を求めて、minidp[i]>0\min_i \operatorname{dp}[i] >0になるような最短の区間を探せばよい。

具体的には、最初に

S(i):=S(i) := キャラiiが勝てないキャラの集合(同キャラ含む)

と言うデータを作っておく。区間を伸ばすときはdp\operatorname{dp}全体に1を足してから各kS(i)k \in S(i)についてdp[k]=1\operatorname{dp}[k] \mathrel{{-}{=}} 1とし、区間を縮めるときにはdp\operatorname{dp}全体から1を引いてから各kS(i)k \in S(i)についてdp[k]+=1\operatorname{dp}[k] \mathrel{{+}{=}} 1とする。dp\operatorname{dp}にセグメント木等を使えば簡単。


ようやく尺取り法の間違えにくい書き方にたどり着いたのでメモ。

  • ok関数とng関数で相互再帰する。
  • 常に半開区間[l,r)[l, r)を扱う。(ok l r)[l,r)[l, r)が条件を満たすときに呼び出され、(ng l r)[l,r)[l, r)が条件を満たさないときに呼ばれる。rはこれから追加される位置であり、lはこれから除去される位置である。
  • (ok l r value)のように区間に関する値を受け渡す場合、valueは常に区間[l,r)[l ,r)に対応する値になるようにする。
  • 最適値を求める場合はok関数の冒頭で更新する。

2019年12月9日月曜日

JOI2018 本戦 B - 美術展

最初からA1A2...ANA_1 \le A_2 \le ... \le A_Nであると仮定してよい。この場合、大きさの最大と最小が決まっていればその範囲にある美術品はすべて展示したほうが得なので、美術品の連続部分列だけ考えればすむ。

美術品iiを展示しているとき、さらにi+1i+1を展示すると得点はBi+1(Ai+1Ai)B_{i+1}-(A_{i+1}-A_i)だけ増えることに注目してDPする。dp[i]:=i\operatorname{dp}[i] := iまで展示するとした時の最大得点、として以下のように更新していけばよい:

dp[i]max(dp[i1]+Bi(AiAi1),Bi) \operatorname{dp}[i] \leftarrow \max(\operatorname{dp}[i-1] + B_{i}-(A_i-A_{i-1}), B_{i})

答えはmaxi[N]dp[i]\max_{i \in [N]} \operatorname{dp}[i]


いわゆるKadane's algorithmっぽい感じなんだけど、なぜかそれをそのまま書いてしまってWAした。

2019年12月2日月曜日

JOI2010 春合宿 - Stairs

xx段目の地面からの高さをHxH_xとし、xx段目までの上り方の総数をf(x)f(x)とする。HlHxPH_l \ge H_x - Pとなるような最小の段をll段目とするとき、ffの漸化式は

f(x)=t[l,x)f(t) f(x) = \sum_{t \in [l, x)}f(t)

と書ける。このままDPすると間に合わないので累積和S(x):=t=0xf(t)S(x) := \sum_{t=0}^xf(t)を考える。

f(x)=S(x1)S(l1) f(x) = S(x-1) - S(l-1) S(x)=f(x)+S(x1) S(x) = f(x) + S(x-1)

が成り立つから、SSの漸化式は

S(x)=2S(x1)S(l1) S(x) = 2S(x-1) - S(l-1)

とまとまる。SSに関してDPすればよい。答えはf(N)=S(N)S(N1)f(N) = S(N)-S(N-1)である。

毎回llを二分探索で求めると計算量はO(NlogN){O}(N\log N)。尺取り法でO(N){O}(N)

2019年12月1日日曜日

JOI2010 本戦 C - つらら

つららvvよりv+1v+1のほうが長いとき、vvv+1v+1LLに達して00に戻るまでは伸びない。この時v+1v+1からvvに辺を張ることにすると、DAGが得られる。そこで、トポロジカルソートした上で辺(u,v)(u, v)に対して

dp[v]max(dp[v],dp[u]+Lav) \operatorname{dp}[v] \leftarrow \max(\operatorname{dp}[v], \operatorname{dp}[u] + L - a_v)

と更新していけば、最終的にはdp[v]=v\operatorname{dp}[v] = v00に戻るまでの時間、となる。dp\operatorname{dp}の初期値はdp[v]:=Lav\operatorname{dp}[v] := L - a_vとしておくのがわかりやすそう。O(N){O}(N)

2019年11月27日水曜日

JOI2008 春合宿 - ナイルドットコム

f(x):=xf(x) := x日目を終えた時点での最小金額 とするとき、

f(x)=min{f(x1)+1日間同じ店で購入する場合の最小金額f(x2)+2日間同じ店で購入する場合の最小金額...f(0)+x日間同じ店で購入する場合の最小金額 f(x) = \min \begin{cases} f(x-1) + 1日間同じ店で購入する場合の最小金額 \\ f(x-2) + 2日間同じ店で購入する場合の最小金額 \\ ... \\ f(0) + x日間同じ店で購入する場合の最小金額 \end{cases}

となっている。上から順に、つまりk=1,2,...,xk=1, 2, ..., xという順にkk日間同じ店で購入する場合の最小金額を求めていくのは、ひとつ前の情報を利用すればO(N){O}(N)で可能なので、全体の計算量はO(ND2){O}(ND^2)になる。

この方針だと割り算を排除しないと通らなかった。実際のループ回数は2×1082 \times 10^8くらい?


もっとましな方法があるらしいのであとで確認する。というか、2008年当時なら通ってなさそう。

2019年11月20日水曜日

KUPC 2014 I - Rain

合計雨量の差にしか興味がないので、費用did_iを使って呪文iiを唱えると地域bib_iの雨量が11減り地域aia_iの雨量が11増える、つまり雨量が11移動するとだけ考えればよい。雨量の移動を自由に繰り返して最小コストで雨量を平準化するのは、最小費用流問題そのものである。

最初の雨量はどの地域も00とし、呪文c1,...,cMc_1, ..., c_Mを唱えて雨量がp1,...,pKp_1, ..., p_Kになったとする。グラフは次のように作ればよい:

  • pv>0p_v>0なら、始点SSからvvに容量pvp_v、コスト00の辺を張る。
  • pv<0p_v<0なら、vvから終点TTに容量pv-p_v、コスト00の辺を張る。
  • bib_iからaia_iに容量\infty、コストdid_iの辺を張る。

SSの出容量のぶんだけTTに流したときの最小コストが答えであり、必要な量を流せない場合は1-1である。計算量(の一例)はO(KElogV){O}(KE \log V)

2019年11月19日火曜日

JOI2011 本戦 D - 歩くサンタクロース

ひとまず一次元の問題として考える。つまり、数直線上のある地点ttに降り立って、各点x1,x2,...,xnx_1, x_2, ..., x_nに行って帰る問題である。さらにx1x2...xnx_1 \le x_2 \le ...\le x_nと仮定する。最後に訪れる点をxkx_kとする時、総距離は

 2tx1+....+2txk1+txk+2txk+1+...+2txn \ 2|t-x_1| + .... + 2|t-x_{k-1}| + |t-x_{k}| + 2|t-x_{k+1}| + ... +2|t-x_n|

となる。絶対値関数の和は下に凸になり、区分する点の中央値で最小値を取ることを使えそうな気がする。しかし、係数が邪魔なので開いて並べる:

tx1,tx1,...,txk1,txk,txk+1,...,txn,txn |t-x_1|, |t-x_1|, ... , |t-x_{k-1}|, |t-x_{k}|, |t-x_{k+1}|, ... , |t-x_n|, |t-x_n|

これら2n12n-1個の点の真ん中にある点xlx_lは(奇数個だから)一点に定まるので、上の式にt=xlt=x_lを代入すると最小値が求まる。具体的には、右側にあるn1n-1個のxix_iを足して左側にあるn1n-1個のxix_iを引けばよい。最後に訪れる点をnn通り試せば答えがわかる。二次元の場合もまったく同じで、xx方向とyy方向について独立に求めてから和を取ればよい。

実装について。いろいろやり方はありそうだけど、ソートされた列に順序を保って挿入・削除する操作があって、さらに区間の和が高速に求まれば直接的にシミュレーションできるのでそれがもっとも簡単に見える。平衡二分木ならどちらもO(logN){O}(\log N)でできる。


けっきょく真ん中になりえる点は1個か2個しかないので、それを試すだけでよいらしい。途中で気づきたかった。

2019年11月13日水曜日

JAG Summer Camp 2013 Day 2 G - Perm Query

与えられた置換ppを巡回置換の合成σ1σ2...σk\sigma_1\sigma_2 ... \sigma_kに分解して、ある巡回置換σ\sigmaに注目する。σ\sigmaの長さがddとすると、

  • σx\sigma xの区間[l,r][l, r]の総和
  • σ2x\sigma^2 xの区間[l,r][l, r]の総和
  • ...
  • σdx=x\sigma^d x = xの区間[l,r][l, r]の総和

をすべて足せばこの巡回置換を一周した場合の総和が求まることになる。この際、巡回置換に現れるすべての数は同じ回数だけ[l,r][l ,r]に現れるので、上の総和は巡回置換に登場する数の総和の倍数になっている。

また、問題のようにすべての巡回置換を同時に適用した場合、次にもとに戻るのは(区間[l,r][l, r]に登場する)すべての巡回置換の長さの最小公倍数である。これをLCM(l,r)\operatorname{LCM}(l, r)とすると、LCM(l,r)\operatorname{LCM}(l, r)回の操作を行う場合、長さddの巡回置換についてはLCM(l,r)/d\operatorname{LCM}(l, r)/d周することになる。この際、巡回置換に登場する数で最初に区間[l,r][l, r]に含まれていたものがqq個であるとすると、巡回置換に登場する数はそれぞれqLCM(l,r)/dq\operatorname{LCM}(l, r)/d回足されることになる。


実装で迷った末に、Moのアルゴリズムを使った。

区間[l,r][l, r]についての値がわかっているとき、[l,r+1][l, r+1]の値を考える。まず、区間が伸びたことによって操作回数がLCM(l,r)\operatorname{LCM}(l, r)からLCM(l,r+1)\operatorname{LCM}(l, r+1)に伸びるので、既存の値がLCM(l,r+1)/LCM(l,r)\operatorname{LCM}(l, r+1)/\operatorname{LCM}(l, r)倍になる。また、位置r+1r+1が追加されるので、この位置を含む巡回置換σ\sigmaの長さddについて、(巡回置換σに登場する数の総和)×LCM(l,r+1)/d(巡回置換 \sigma に登場する数の総和) \times \operatorname{LCM}(l, r+1)/dが加算されることになり、新しい値が求まる。区間が縮む場合も同様に書ける。

LCM(l,r)\operatorname{LCM}(l, r)の値を得るのにsparse tableが使えるから計算量の問題はないと思って書き出したのだが、伸縮の際にmodの割り算が必要になるので結局log\logがついてO(NQlogN){O}(N \sqrt{Q} \log N)だった。[1]


lcmと同時に、それぞれの巡回置換の総和もセグメント木で持てばよいらしい。それはそうか……


  1. lcmの範囲が大きいので逆数のテーブルは用意できない。でも、うまい方法がありそうな気もする。 ↩︎

2019年11月10日日曜日

ARC 014 - 魂の還る場所

隣り合う同じ色の2つのボールは即消せるので、その部分は最初から存在しないものとしてよい。また、GBGのように1つ挟んで同じ色のボールも消すことができて、間のBは左右の好きなほうに置くことができるので、やはり2つのGは最初から存在しないものとしてよい。これらのパターンを連鎖的に消していけば、残るのは距離2以内に同じ色のボールがない並びのみで、これはRGBRGBRGB...のように周期3の列になっている。そしてやはりこのパターンも各色の偶数個分は全部消せる。つまり、各色の偶奇を判定して00から33までのいずれかを答えにすればよい。

2019年11月8日金曜日

ARC 049 B - 高橋ノルム君

ARC 049 B - 高橋ノルム君

時間ttが与えられたとき、ii人目のノルム君の行ける範囲は[xit/ci,xi+t/ci]×[yit/ci,yi+t/ci][x_i-t/c_i, x_i + t/c_i] \times [y_i -t/c_i, y_i + t/c_i]となる。これらのNN個の区間の共通部分が空でないことと、時間tt内にすべてのノルム君が一点に到達できることは同値である。したがって、ttについて二分探索すればよい。


判定の部分を深く考えずに平衡二分木上のいもす法でやってしまったのだが、よく考えると区間の両端を保持するだけでよかった。

2019年11月5日火曜日

ARC 044 B - 最短路問題

ARC 044 B - 最短路問題

とりあえずソートしてランレングス圧縮して、同じ距離の頂点グループ毎にグラフを作っていくことを考える。距離00は頂点11だけとか、距離が飛んでいる部分があってはいけないとか、そういうケースは最初に考慮しておく。

頂点11からの距離がiiの頂点がBiB_i個あるとする。距離iiの頂点同士はどうつないでもほかの頂点と11との距離に影響しない。したがって、(Bi2)\binom{B_i}{2}通りのペアすべてに自由に辺を張れるので、距離iiに対して2(Bi2)2^{\binom{B_i}{2}}通りの辺の張り方があることになる。

次に、距離i1i-1の頂点グループと距離iiの頂点グループをつなぐ辺の張り方を考える。これもだいたい自由に辺を張れるのだが、距離iiの頂点は、距離i1i-1Bi1B_{i-1}個の頂点のうち少なくとも1つにはつながれていなければならないという制約がある。「少なくとも1つ」は包除原理で書ける:

2Bi1Bi(Bi1)2Bi1(Bi1)+(Bi2)2Bi1(Bi2)...+(1)Bi(BiBi)2Bi10=k=0Bi(Bik)(1)k(2Bi1)Bik=(2Bi11)Bi\begin{aligned} & 2^{B_{i-1}B_i} - \binom{B_i}{1}2^{B_{i-1}(B_i-1)} + \binom{B_i}{2}2^{B_{i-1}(B_i-2)} - ... + (-1)^{B_i}\binom{B_i}{B_i}2^{B_{i-1} \cdot 0} \\ = & \sum_{k=0}^{B_i} \binom{B_i}{k}(-1)^k(2^{B_{i-1}})^{B_i-k} \\ = & (2^{B_{i-1}}-1)^{B_i} \end{aligned}

あっ……


包除で求めてから自明な数え方に気づくパターン、これで3回目くらい。

2019年10月30日水曜日

JOI2018 予選 F - L番目のK番目の数

JOI2018 予選 F - L番目のK番目の数

0-based(A0,A1,...,AN1A_0, A_1, ..., A_{N-1})で考える。

書き出した数の中にxx以下の数がf(x)f(x)個あるとするとき、f(x)Lf(x) \ge Lとなるような最小のxxを求めればよい。この言い換えを元の数列(Ai)(A_i)の言葉でさらに言い換えると、次のようになる:

xx以下の数をKK個以上含むような部分列A[l,r)A[l, r)LL個以上存在するようなxxの中で最小のものを求めよ。

部分列A[l,r)A[l, r)xx以下の数をg(l,r,x)g(l, r, x)個含むとする。xxを固定した時にg(l,r,x)K、g(l, r, x) \ge Kであるようなl,r[0,N]l, r \in [0, N]が何組あるか数えられれば、二分探索で上のxxも求まりそう。具体的には次のように数える:

g(0,i,x)g(0, i, x)は部分列A[0,i)A[0, i)の中のxx以下の数の総数だから、累積和でテーブルを作っておけば任意のiiについてO(1)O(1)で求まる。そして、g(l,r,x)=g(0,r,x)g(0,l,x)Kg(0,r,x)K+g(0,l,x)g(l, r, x) = g(0, r, x) - g(0, l, x) \ge K \Leftrightarrow g(0, r, x) \ge K+g(0, l, x)であり、llを固定した時にこの式を満たすようなrrの数は二分探索で求まる。したがって、すべてのl[0,N]l \in [0, N]についてこのrrの数を求めて足し合わせると、それがf(x)f(x)である。

あとはf(x)f(x)について二分探索して、f(x)Lf(x) \ge Lであるような最小のxxを求めればそれが答えになっている。計算量はO(Nlog2N){\mathcal O}(N \log^2 N)

ARC 024 C - だれじゃ

ARC 024 C - だれじゃ

「(部分列に)文字ccがちょうどxx個含まれること」の26N26N通りすべてに対して、あらかじめ乱数Rc,xR_{c, x}を割り振っておく。 最初のKK文字のヒストグラムを作って、各文字ccについて該当するRc,xR_{c, x}のXORを取って、最初のハッシュ値とする。

あとは、長さKKの部分列をスライドさせながら各部分列のハッシュ値を求めていく。直前の部分列のハッシュ値がVVであったとし、文字ccが加わってccの数がxxからx+1x+1に増え、文字ddが除かれてddの数がyyからy1y-1に減るとすると、新しい部分列のハッシュ値はVRc,xRc,x+1Rd,yRd,y1V \oplus R_{c, x} \oplus R_{c, x+1} \oplus R_{d, y} \oplus R_{d, y-1}になる。このハッシュ値をキーとして、部分列の開始位置を保持しておく。同じハッシュ値が再び出現した時、開始位置がKK以上離れていればアナグラムダジャレが見つかったことになる。

アルファベットのサイズをα\alphaとするとき、計算量はO(αN){\mathcal O}(\alpha N)


こんなことしなくても、普通にヒストグラムをキーにするだけで通った……