2020年8月22日土曜日
2020年8月17日月曜日
CodeChef August Challenge 2020 - Insanely Hard School Exam
1つの色についてだけ考える。平行な2直線がないとすると、直線がx本ある時のtruly-geometric triangle(以下TGT)の総数は(3x)である。これを元に、dp[x][y]=色xまで見て消しゴムの残量がyである場合のTGTの総数、として全体でO(NK)のDPが可能なのでsubtask2, 3が解ける。
平行線がある場合について考える。平行関係は同値関係なので、異なる同値類の総数をpとし、i番目の同値類に属する直線がLi個あるとする。L1≥L2≥...≥Lpと仮定してよい。これらの直線を消していくとき、同値類の総数をできるだけ減らすように消すのが最善なので、後ろから消していくのがよい。そこでf(c,z)=色cの直線がz本残っている場合のTGTの総数、としてfを事前に求めておけば、上と同じDPで解ける。順序が固定されているのでf自体もDPで求まる。
CodeChef August Challenge 2020 - Subsequence Frequency Counting
数列(Ai)が整数xをcx個含むとする。また、整数xが代表になるような部分列でちょうどk個のxを含むものの総数をf(x,k)とする。f(x,k)を二項係数で表すと、以下のようになる:
f(x,k):=(i=0∑k−1(ic1)...i=0∑k−1(icx−1))(kcx)(i=0∑k(icx+1)...i=0∑k(icN))f(x,k)をN通りの引数について足せばよいが、毎回すべての二項係数を掛け算していては間に合わない。そこで、dp[x]:=∑i(icx)という形の列をセグメント木で持っておくことを考える。dpのそれぞれの値が適切なkに対応していれば、f(x,k)はdp[1...x−1]の積と (kcx)とdp[x+1...N]の積を掛けて求まることになる。k=0からfを求めていくとして、dpの末尾からkが増えていくので、kの昇順、タイブレークはxの降順にf(x,k)を求めつつdpを更新していくと、一回の更新がO(logN)でできる。
CodeChef August Challenge 2020 - Chefina and Strange Tree
与えられたグラフを根付き森とみなすと、ある頂点vを消す操作は、vのそれぞれの子の部分木とvの親側の木とをすべて切り離す操作と考えられる。オイラーツアー(pre-order)上で考えると、部分木は連続した区間に対応しているので、区間を切り貼りすることができればよい(下図)。そこでオイラーツアーを平衡二分木で保持することにする。
平衡二分木にminを載せて、さらに遅延更新で加算ができるようにする。町Piについての処理の流れは次のようになる:
- Piを含む木T(=1つの平衡二分木で表されたオイラーツアー)のすべての頂点の値からAPiを引く。
- Tのminを求めて、0以下ならT上の二分探索で最小値を取る頂点vを探す。vがi日目に0になったと記録し、vの値を+∞にセットする(再び0にならないようにするため。) minが0より大きくなるまでこれを繰り返す。
- 上図のようにPiの隣接頂点に関する部分木をすべて切り離す。
1を行う際に、Piを含む平衡二分木を得る必要があるが、永続UnionFindを使った。事前に列(Pi)を逆順に走査しながら頂点をつないでおく。Piを(正順に)処理する際には、その時点でのPiに対応する(UnionFind上の)根に平衡二分木を持たせるようにした。(でも、ちゃんと前処理すれば普通のUnionFindでできるか。)
計算量は全体でO(NlogN)。
明らかに過剰なデータ構造をごちゃごちゃと貼ってしまった。physics0523さんの解説によると、操作の構造自体が木になっていることを利用できるらしい。なるほど……
2020年8月9日日曜日
AGC 037 B - First Second
文字列を反転して条件を言い換えると、文字列s,t,(∣s∣≤∣t∣)に関する問題の条件は次のようになる。(添え字はPython風に表している)
- tのprefixにsの最後の1文字を除いたものs[:−1]が含まれ、
- さらにsの最後の1文字s[−1]がtのそれ以降に存在する
1つ目の条件だけ考える場合、事前にTrieを作っておけばs[:−1]をprefixとして含むような文字列の総数は高速に数えられる。この時、s[:−1]をTrieで辿った際の終端ノードに2つ目の条件に関する情報があれば、うまく数えられそうに見える。具体的には、そのノードの部分木に含まれる文字列でs[−1]を含んでいるものの数が記録されていれば、2つ目の条件も満たすものが数えられる。
そこで、配列dpvをdpv[c]:=vの子に属する文字列で以降に文字cを含むものの総数、と定めて、Trieの各ノードに持たせればよい。
アルファベットのサイズをαとするとき、このdp配列は各ノードについてならしO(α)で作れるので、全体の計算量はO(α∑iSi)。(一つの子ノードについて、親への遷移はちょうど1回で、遷移の計算量がO(α)。)