2021年3月29日月曜日

CodeChef - Unusual Queries

CodeChef - Unusual Queries

iiの「次の山」は、i<ji<jかつHi<HjH_i < H_jであるような最小のjjである。(→ next greater element) 各山に向かって次の山から辺を張ったグラフは有向森であり、この森にHN+1=H_{N+1}=\inftyに対応する大頂点を加えると頂点N+1N+1を根とする有向木(out-tree)になる。この木をTTとする。この時、クエリはTTの一部を切り取ったグラフの最も長いパスの頂点数を質問していることになる。

  • クエリ(L,R)(L, R)を処理する時、rrRRより小さくしても得しないので、r=Rr = Rを固定してl[L,R]l \in [L, R]について最大値が求まればよい。これは適当な列が得られていればRMQに帰着するはずだが、どうやってその列を得るかが問題となる。
  • 任意のRRについて、dpR\operatorname{dp}_RdpR[i]:=l=i,r=R\operatorname{dp}_R[i] := l=i, r=Rの時の求める値、と定める。このとき、dpR+1\operatorname{dp}_{R+1}dpR\operatorname{dp}_R上のR+1R+1の部分木に含まれる各位置に+1したものになっている。
  • TTの頂点番号はTTの帰りがけ順についているので、部分木への加算はdpR\operatorname{dp}_Rのある区間への加算になっている。

したがって、クエリ先読みができるならdpR\operatorname{dp}_Rを区間max・区間addの遅延セグメント木で持ってRRの昇順に更新しつつ、各RRに対応するクエリをまとめて処理すればよい。満点を取るにはオンラインで処理する必要があるが、これは永続化でできる。

この問題の場合はStarry Sky treeでいいので、普通の永続セグメント木と同じように素朴なpath copyingでできる。


わからなかった。上の方針はEditorialの方針とanubhavdharさんの書き込みのハイブリッドみたいなやつ。

2021年3月22日月曜日

小さい数を法とする二項係数

(nk)mod  M\binom{n}{k} \mod Mを求めたいが、MMが合成数だったりkMk \ge Mだったりして階乗の逆元が得られない場合について。nnが小さければパスカルの三角形を作ればよいが、MMが小さい場合にも方法がある。https://discuss.codechef.com/t/your-approach-to-solve-sandwich/14618/13 より。

MMをいくつかの互いに素な因数に分解してそれぞれを法として二項係数を求めれば、CRTでmod  M\mod Mの値が得られる。したがって、次の問題が解ければよい:

問題: 素数の累乗pep^eが与えられた時に(nk)mod  pe\binom{n}{k} \mod p^eを求めよ。

f(x,p):=f(x, p) := 階乗x!x!を非負整数e,qe, qを使ってx!=peqx! = p^e qと表す時の最大のqq

とする。言い換えると、f(x,p)f(x, p)x!x!ppで割れる限り割ったものである。n!=pe1f(n,p),k!=pe2f(k,p),(nk)!=pe3f(nk,p)n! = p^{e_1}f(n, p), k! = p^{e_2}f(k, p), (n-k)! = p^{e_3}f(n-k, p)とするとき、二項係数(nk)\binom{n}{k}は次のように書ける:

(nk)=pe1e2e3f(n,p)f(k,p)f(nk,p) \binom{n}{k} = \frac{p^{e_1-e_2-e_3}f(n, p)}{f(k, p)f(n-k, p)}

f(k,p),f(nk,p)f(k, p), f(n-k, p)pep^eと互いに素なのでmod  pe\mod p^eで逆元を持つ。したがって、e1,e2,e3e_1, e_2, e_3f(n,p),f(k,p),f(nk,p)f(n, p), f(k, p), f(n-k, p)が求まれば二項係数を計算できる。

前者、つまりe1,e2,e3e_1, e_2, e_3を求めるほうは簡単で、一般に階乗x!x!ppで割れる回数はi=1x/pi\sum_{i=1}^\infty \lfloor x/p^i \rfloorに等しく(ルジャンドルの公式)、この式で素朴に計算すればO(logx){O}(\log x)で求まる。

次にmod  pe\mod p^ef(x,p)f(x, p)を計算する問題を考える。f(x,p)f(x, p)x!x!から素因数ppを除いたものなので、ひとまず階乗からppの倍数だけ分離して考えてみる: XX1,2,...,x1, 2, ..., xのうちのppの倍数でない数の総積とするとき、f(x,p)=Xf(x/p,p)f(x, p) =Xf(\lfloor x/p \rfloor, p)が成り立ち、O(logx){O}(\log x)回の再帰でf(0,p)=1f(0, p)=1に帰着してf(x,p)f(x, p)が求まる。 (実際にはループでよい。)

あとはXXmod  pe\mod p^eで求める方法が問題になるが、ppの倍数の位置はmod  pe\mod p^eで周期的なことを利用できる。最初に「ppの倍数だけ飛ばした階乗mod  pe\mod p^e」の長さpep^eのテーブルを作っておけば、XXは繰り返し二乗法でO(logx){O}(\log x)で計算できる。

全体の計算量は

  • build: O(pe){O}(p^e)
  • query: O((logn)2){O}((\log n)^2)

と評価できる。また、冒頭の任意のMMを法とした二項係数を求める問題に関しては、

  • build: O(M){O}(M)
  • query: O((logn)2loglogM){O}((\log n )^2 \log \log M)

となる。(loglogM\log \log MMMの異なる素因数の個数の評価。)


Lucasの定理の素数の累乗に関する拡張を使う方法もあるらしい。たぶんこちらのほうが速い。 https://arxiv.org/pdf/1301.0252.pdf

2021年3月17日水曜日

ARC 065 E - へんなコンパス

a,ba, bの距離をDDとする。全体を45度回転してチェビシェフ距離の問題に読み替えると、1つの点(x,y)(x, y)から距離DDにある点はx±D,y±Dx \pm D , y\pm Dで定まる正方形の辺上にある。したがって、

  • v[t]:=xv[t] := x座標をttとする点をyy座標の昇順に並べた列
  • h[t]:=yh[t] := y座標をttとする点をxx座標の昇順に並べた列

を持っておけば、(x,y)(x, y)から距離DDにある点の数は4つの列v[x±D],h[y±D]v[x \pm D], h[y \pm D]の二分探索で数えられる。

ただ、問題が要求しているのは、単にマンハッタン距離がDDであるような点のペアを数えることではなく、そのような点を結ぶ無向辺があるようなグラフを考えた時、辺{a,b}\{a, b\}を含む連結成分について同じ量を数えることである。

ただ、この設定でもBFSをしながら同じことをやればよいように見える。つまり、aaを始点にして、上記の数え上げをしながら距離DDにある点をキューに入れていけばよい……が、これを素朴にやるとΩ(N2)\Omega (N^2)になってしまう。o(N2)o(N^2)で数えるには、一度見た頂点を再び走査しないようにするために、その都度消していく必要がある。

そこで、h(t),v(t)h(t), v(t)をそれぞれ昇順の配列hs(t),vs(t)h_s(t), v_s(t)と平衡二分木hb(t),vb(t)h_b(t), v_b(t)の二通りで持つことにする。キューから点(x,y)(x, y)を初めて取り出した時、次の処理をする:

  • (x,y)(x, y)をすべての平衡二分木から消去する。
  • vb[xD],vb[x+D],hb[yD],hb[y+D]v_b[x - D], v_b[x+D], h_b[y - D], h_b[y+D]からそれぞれ区間[yD,y+D],[yD,y+D],[xD,x+D],[xD,x+D][y-D, y+D], [y-D, y+D], [x-D, x+D], [x-D, x+D]に対応する4列をsplitして取り出す。vb[xD],vb[x+D],hb[yD],hb[y+D]v_b[x - D], v_b[x+D], h_b[y - D], h_b[y+D]には残った左右の区間をmergeして入れておく。
  • 4つの列上の頂点をすべてキューに入れ、vb,hbv_b, h_bからすべて除く。
  • (x,y)(x, y)と4つの列上の点によるペアを数える。
    • 配列vs[xD]v_s[x-D][yD,y+D][y-D, y+D]に対応する区間の長さを結果に加算する。
    • 配列vs[x+D]v_s[x+D][yD,y+D][y-D, y+D]に対応する区間の長さを結果に加算する。
    • 配列vs[yD]v_s[y-D][xD,x+D][x-D, x+D]に対応する区間の長さを結果に加算する。
    • 配列vs[y+D]v_s[y+D][xD,x+D][x-D, x+D]に対応する区間の長さを結果に加算する。
    • 正方形の4頂点(x±D,y±D)(x \pm D, y \pm D)に穴が存在する場合は2回数えているので、重複を引く。

すべてのペアを2回ずつ数えているので、最後に2で割ることに注意。

2021年3月14日日曜日

AHC 001 A - Atcoder Ad

AHC001

49,567,989,183で暫定17位だった。→システス後18位だった。

焼きなましをした。遷移は上のように隣の広告に割り込むような操作とそのバリエーションを中心にしたが、広告の単純な伸縮も残した。温度はかなり高めに設定していて伸縮のほうはほぼ通るので、単に状態の多様性を生むだけの遷移になっていたと思う。

以下は効果が大きかったもの:

  • しばらく最大スコアが更新されなかったら、スコアの低い広告周辺を上の操作で無理やり変形するようなキックを入れた。
  • 近い広告は基本的にはくっついていたほうが遷移がしやすいので、得点計算の式を希望より大きいぶんにはかまわない(pi=1(1min(1,si/ri))2p_i = 1- (1- \min(1, s_i/r_i))^2)というものにして、最後に縮めることにした。

以下、ほかの人の解法等を眺めての感想。

  • うまくいっていない部分が(スコアの低い広告という形で)特定しやすいし、その周辺をなんとかすれば問題ないだろうという想像でキックの調整を頑張っていたが、多点スタートをもう少しちゃんと考えるべきだったかも。
  • キックで1×11\times1に縮める、はなるほどと思った。自分の複雑な操作よりよほど単純で効果的に見える。
  • 最後まで配布ツールによるもの以外のビジュアライズを一切せずに結果を見て良くない部分を直すような操作を考えていたが、動画などで見てわかることも多そうだと思った。次は準備したい。
  • それっぽい変形を書いては調整を頑張るだけに時間を費やしていて、問題の性質について真剣に考えている時間が短かったな。(試すことリストを消化するだけで時間が過ぎていって、スコアもまあまあ伸びたので考えるタイミングがなかったというのはある。)順位がよかったのはたまたまだろうなと思った。

2021年3月4日木曜日

JOI2021 本選 C - 集合写真

以下、すべて0-basedに直して考えている。

最終形は2104387652 1 0 |4 3|8 7 6 5のように、0,1,...,N10, 1, ..., N-1をいくつかの連続部分列に区切って各区間を反転させたものになっている。

dp[x]=\operatorname{dp}[x] = 先頭からxx個の階段に0,1,...,x10, 1, ..., x-1を有効な順番で並べる場合の最小交換数

としてDPができそう。

DPの遷移を考える。位置0,1,...,x10, 1, ..., x-1に人0,1,...,x10, 1, ..., x-1を配置済みとして、位置xxからx,x+1,...,y1x, x+1, ..., y-1を逆順に並べる場合のコストをf[x][y]f[x][y]とすると、dp[y]max(dp[y],dp[x]+f[x][y])\operatorname{dp}[y] \leftarrow \max(\operatorname{dp[y]}, \operatorname{dp}[x] + f[x][y])のように配るDPがO(N2){O}(N^2)でできる。

次にffの求め方を考える。与えられた列の中に散らばっている数x,x+1,...,y1x, x+1, ..., y-1を位置x,x+1,...,y1x, x+1, ..., y-1に逆順で整列させる時、c[i][j]:=c[i][j] :=iiの左にあるjj以上の数の総数として、だいたいc[x][x]+c[x+1][x]+...+c[y1][x]c[x][x] + c[x+1][x] + ... + c[y-1][x]回の交換をしてx,x+1,...,y1x, x+1, ..., y-1を前のほうに持ってくることになるが、この際、与えられた列上でのx,x+1,...,y1x, x+1, ..., y-1の順番を考えた時に、既に転倒が起きているぶんの交換はしなくてよい。そこで、inv[x][y]:=\operatorname{inv}[x][y] := 与えられた列からx,x+1,...,y1x, x+1, ..., y-1を抜き出した部分列の転倒数、とすると

f[x][y]=c[x][x]+...+c[y1][x]inv[x][y] f[x][y] = c[x][x] + ... + c[y-1][x] - \operatorname{inv}[x][y]

と求まる。ccは累積和と同じように構成できて、さらにその累積和C[x][y]:=c[0][y]+...+c[x1][y]C[x][y] := c[0][y] + ... + c[x-1][y]を取っておけば

f[x][y]=C[y][x]C[x][x]inv[x][y] f[x][y] = C[y][x] - C[x][x] - \operatorname{inv}[x][y]

と求まる。

最後にinv\operatorname{inv}の作り方を考える。ここではinv[x][y1]\operatorname{inv}[x][y-1]inv[x][y]\operatorname{inv}[x][y]の差分、つまり、与えられた列上のx,x+1,...,y2x, x+1, ..., y-2に対応する部分列に(与えられた順番通りに)y1y-1を挿入した時の転倒数の増分を考える。この増分は与えられた列上でy1y-1の後ろにあって[x,y1)[x, y-1)に含まれる数の総数に等しい。y1y-1の後ろよりも前にある数のほうが(ccを使って)表しやすいので、さらにこれを(y1x)(y-1-x) -(y1y-1の前にあって[x,y1)[x, y-1)に含まれる数の総数)と言い換える。この増分は上で求めたccを使って表せて、結局

inv[x][y]inv[x][y1]+(y1x)(c[y1][x]c[y1][y]) \operatorname{inv}[x][y] \leftarrow \operatorname{inv}[x][y-1] + (y-1-x) - (c[y-1][x] - c[y-1][y])

と遷移できる。

これで、全体としてO(N2){O}(N^2)で計算できたことになる。