2020年8月22日土曜日

yukicoder No.1191 数え上げを愛したい(数列編)

条件を満たす数列のすべての要素は互いに異なるので、昇順のものだけ数えて後でN!N!を掛ければよい。

S1S_1が与えられたとき、残りの項は

f(k):=f(k) := 11回の操作でAAマス以上進むとして、N1N-1回操作してちょうどkkマス進む場合の数

と考えると形としてはf(A)+f(A+1)+...+f(B)f(A)+f(A+1)+...+f(B)通りのように求まる。(実際にはS1+kMS_1+k \le Mであるようなf(k)f(k)までしか足せない)したがって、ffかあるいはその累積和F(k)=i=1kf(i)F(k) = \sum_{i=1}^kf(i)が求めればS1=1,2,...,NS_1 = 1, 2, ..., Nについて足せばよいことになる。形式的べき級数で考えると

f(k)=[xk](xA+xA+1...)N1=[xk](xA1x)N1=[xkA(N1)](1x)(N1)=[xkA(N1)]i=0(i+N2N2)xi=(kA(N1)+N2N2) \begin{aligned} f(k) & = [x^k](x^A+x^{A+1}...)^{N-1} \\ & =[x^k]\Bigl(\frac{x^A}{1-x}\Bigr)^{N-1} \\ & =[x^{k-A(N-1)}](1-x)^{-(N-1)} \\ & = [x^{k-A(N-1)}] \sum_{i=0}^\infty \binom{i+N-2}{N-2}x^i\\ & = \binom{k-A(N-1)+N-2}{N-2} \end{aligned}

F(k)F(k)を求めるなら、(1x)1(1-x)^{-1}をもう一つ掛けることになるので(kA(N1)+N1N1)\binom{k-A(N-1)+N-1}{N-1}となる。

2020年8月17日月曜日

CodeChef August Challenge 2020 - Smallest KMP

最適な文字列がどういう形になっているか考えると、パターンの前後はa...ab...b ...a...ab...b \ ...... y...yz...z... \ y...yz...zのようになっているはずなので、SSからPPを除いた上でソートしたものをSS'とし、PPSS'のどこに挿入するのが最善か調べる。PPSS'ii文字目に挿入したものとSS'i+1i+1文字目に挿入したものを比較するとき、この比較は辞書順に関して下に凸になっているので、最小となる場所を二分探索で探せばよい。

CodeChef August Challenge 2020 - Insanely Hard School Exam

1つの色についてだけ考える。平行な2直線がないとすると、直線がxx本ある時のtruly-geometric triangle(以下TGT)の総数は(x3)\binom{x}{3}である。これを元に、dp[x][y]=\operatorname{dp}[x][y] =xxまで見て消しゴムの残量がyyである場合のTGTの総数、として全体でO(NK){O}(NK)のDPが可能なのでsubtask2, 3が解ける。

平行線がある場合について考える。平行関係は同値関係なので、異なる同値類の総数をppとし、ii番目の同値類に属する直線がLiL_i個あるとする。L1L2...LpL_1 \ge L_2 \ge ... \ge L_pと仮定してよい。これらの直線を消していくとき、同値類の総数をできるだけ減らすように消すのが最善なので、後ろから消していくのがよい。そこでf(c,z)=f(c, z) =ccの直線がzz本残っている場合のTGTの総数、としてffを事前に求めておけば、上と同じDPで解ける。順序が固定されているのでff自体もDPで求まる。

CodeChef August Challenge 2020 - Subsequence Frequency Counting

数列(Ai)(A_i)が整数xxcxc_x個含むとする。また、整数xxが代表になるような部分列でちょうどkk個のxxを含むものの総数をf(x,k)f(x, k)とする。f(x,k)f(x, k)を二項係数で表すと、以下のようになる:

f(x,k):=(i=0k1(c1i)...i=0k1(cx1i))(cxk)(i=0k(cx+1i)...i=0k(cNi)) f(x, k) := \biggl( \sum_{i=0}^{k-1} \binom{c_1}{i}...\sum_{i=0}^{k-1}\binom{c_{x-1}}{i} \biggr) \binom{c_x}{k} \biggl(\sum_{i=0}^{k}\binom{c_{x+1}}{i} ... \sum_{i=0}^{k}\binom{c_N}{i} \biggr)

f(x,k)f(x, k)NN通りの引数について足せばよいが、毎回すべての二項係数を掛け算していては間に合わない。そこで、dp[x]:=i(cxi)\operatorname{dp}[x] := \sum_i \binom{c_x}{i}という形の列をセグメント木で持っておくことを考える。dp\operatorname{dp}のそれぞれの値が適切なkkに対応していれば、f(x,k)f(x, k)dp[1...x1]\operatorname{dp}[1...x-1]の積と (cxk)\binom{c_x}{k}dp[x+1...N]\operatorname{dp}[x+1 ... N]の積を掛けて求まることになる。k=0k=0からffを求めていくとして、dp\operatorname{dp}の末尾からkkが増えていくので、kkの昇順、タイブレークはxxの降順にf(x,k)f(x, k)を求めつつdp\operatorname{dp}を更新していくと、一回の更新がO(logN){O}(\log N)でできる。

CodeChef August Challenge 2020 - Chefina and Strange Tree

与えられたグラフを根付き森とみなすと、ある頂点vvを消す操作は、vvのそれぞれの子の部分木とvvの親側の木とをすべて切り離す操作と考えられる。オイラーツアー(pre-order)上で考えると、部分木は連続した区間に対応しているので、区間を切り貼りすることができればよい(下図)。そこでオイラーツアーを平衡二分木で保持することにする。

chefcomp

平衡二分木にmin\minを載せて、さらに遅延更新で加算ができるようにする。町PiP_iについての処理の流れは次のようになる:

  1. PiP_iを含む木TT(=1つの平衡二分木で表されたオイラーツアー)のすべての頂点の値からAPiA_{P_i}を引く。
  2. TTmin\minを求めて、00以下ならTT上の二分探索で最小値を取る頂点vvを探す。vvii日目に00になったと記録し、vvの値を++\inftyにセットする(再び00にならないようにするため。) min\min00より大きくなるまでこれを繰り返す。
  3. 上図のようにPiP_iの隣接頂点に関する部分木をすべて切り離す。

1を行う際に、PiP_iを含む平衡二分木を得る必要があるが、永続UnionFindを使った。事前に列(Pi)(P_i)を逆順に走査しながら頂点をつないでおく。PiP_iを(正順に)処理する際には、その時点でのPiP_iに対応する(UnionFind上の)根に平衡二分木を持たせるようにした。(でも、ちゃんと前処理すれば普通のUnionFindでできるか。)

計算量は全体でO(NlogN){O}(N\log N)


明らかに過剰なデータ構造をごちゃごちゃと貼ってしまった。physics0523さんの解説によると、操作の構造自体が木になっていることを利用できるらしい。なるほど……

2020年8月9日日曜日

AGC 037 B - First Second

文字列を反転して条件を言い換えると、文字列s,t,(st)s, t, (|s| \le |t|)に関する問題の条件は次のようになる。(添え字はPython風に表している)

  1. ttのprefixにssの最後の1文字を除いたものs[:1]s[:-1]が含まれ、
  2. さらにssの最後の1文字s[1]s[-1]ttのそれ以降に存在する

1つ目の条件だけ考える場合、事前にTrieを作っておけばs[:1]s[:-1]をprefixとして含むような文字列の総数は高速に数えられる。この時、s[:1]s[:-1]をTrieで辿った際の終端ノードに2つ目の条件に関する情報があれば、うまく数えられそうに見える。具体的には、そのノードの部分木に含まれる文字列でs[1]s[-1]を含んでいるものの数が記録されていれば、2つ目の条件も満たすものが数えられる。

そこで、配列dpv\operatorname{dp_v}dpv[c]:=v\operatorname{dp_v}[c] :=vの子に属する文字列で以降に文字ccを含むものの総数、と定めて、Trieの各ノードに持たせればよい。

アルファベットのサイズをα\alphaとするとき、このdp\operatorname{dp}配列は各ノードについてならしO(α){O}(\alpha)で作れるので、全体の計算量はO(αiSi){O}(\alpha\sum_i S_i)。(一つの子ノードについて、親への遷移はちょうど1回で、遷移の計算量がO(α){O}(\alpha)。)