2020年1月31日金曜日
2020年1月29日水曜日
JOI2007 春合宿 - lines
同一の直線は最初に除いておく。以下、N本の直線は相異なるとする。
平面走査的に考えてみる。つまり、十分に大きな正数aについてx軸に平行な直線L:y=aを用意して、この直線を下に移動しながら領域の数を数える。y=aと平行な直線はないとしてよい。[1] 最初に直線LはN本の直線と交わっていて、これらに区切られたN+1個の領域にまたがっている。Lを下に移動していくとき、Lが新たな領域に出会うのは直線の交点に一致した瞬間である。このとき、交点を通る直線が2本なら領域が1つ増え、3本なら2つ増え、一般にk本の直線の交点ではk−1本増えている。
以上の観察より、単にl1から順に直線を追加していき、新しい直線を追加するたびにこれまで追加した各直線と交わる点を(平行でなければ)検出し、同じ交点を検出した回数を数えていけばよい。
N本の直線を処理したあと、ある交点Pをちょうど2本の直線が通ったなら、Pは1回数えられ、3本なら1+2=3回数えられる。一般に、k+1本の直線の交点は1+2+...+k=k(k+1)/2回数えられていて、そこで増えた領域はk個になっている。二次方程式を解くと、点Pがv回数えられたならそこで増えた領域は(−1+1+8v)/2個である。計算量はO(N2)。
あったら、全体を適当に回転させたものを考えればよい。 ↩︎
2020年1月26日日曜日
Code Festival 2014 - eject
f(x):=x回目でONになっている確率とすると、漸化式は
f(x)=k∑x−1k回目でOFFで、あとはずっとONである確率=k∑x−1(1−f(k))p(1−p)x−k−1このままでは間に合わないので差分を計算すると、f(x)−f(x−1)=p(1−2f(x−1))になっていて、f(x)=p+(1−2p)f(x−1)という線形漸化式が従う。f(x)−1/2=(1−2p)(f(x−1)−1/2)より、f(x)=1/2+(1−2p)x(f(0)−1/2)=1/2+(1−2p)x⋅(−1/2)と表せて、これなら計算できる形になっている。
……と迂遠な導出をしてしまったが、よく見ると自明にf(x)=p(1−f(x−1))+(1−p)f(x−1)だった。直前から求まるかどうかは常に最初に考えるべき。
p=1の場合に計算誤差が問題になるようだった。(expt -1d0 奇数)
は-1になってほしいが、指数が大きいと1になる。手元だと境界はこの辺:
> (expt -1d0 9007199254740991)
-1.0d0
> (expt -1d0 9007199254740993)
1.0d0
前者(=253−1)がdouble-float
で正確に表現できる最大の整数なので、そこを超えると精度が落ちるのは明らかとして、そこまではOKなのかは実装次第だが、たぶん普通はOK。
2020年1月25日土曜日
CodeChef Aarambh 2020 - Odd Topic
出現する整数が高々4000種類しかないので、4000個のcompact bit vectorを持ってすべてのクエリに対して累積和で計算する、みたいなムーブを最初にやってしまった。(部分点は取れる。) 結局、バケット法+bitset高速化をした。
簡単のために、数列aとbの長さは両方Nとする。aとbを長さB毎に区切ってk:=N/B個の区間に分解し、さらにそれぞれの区間に対して長さ4000のビット配列を保持する。このビット配列をα1,...,αk,β1,...,βkとする。それぞれの区間について、出現する整数の奇遇を数えて
αi[x]:={1(整数xが区間内に奇数個ある場合)0(整数xが区間内に偶数個ある場合)としておく。(βiも同様)
各クエリの指定区間に含まれるバケットαi,βiのXORを取って、端の部分は愚直に数えれば、立っているビットの数が答えになる。出現する値の最大値をAとすると、計算量はO(Q((N/B)⋅(A/logA)+B))。B=Θ(NA/logA)ならO(QNA/logA)。(/logAの部分はいわゆるbitset高速化)
コンテスト中は深く考えずにB=200としたのだけど、計算量を見るとNよりは大きいほうがよさそうだったのでB=1000としてみたら遅くなった。なにか勘違いしてるかも。
tmwilliamlinさんの提出を見たら、単純なbitsetの累積和で処理していた。それはそうだった…… O((N+Q)A/logA)。
Egorさんは出現する整数のほうを下位5ビットごとに分割して、bitsetなしで通している。
2020年1月24日金曜日
ARC 070 D - No Need
x枚目まで見たときに総和がy以上になるようなカードの集合の総数をdp[x][y]とすると、漸化式は
dp[x][y]=dp[x−1][y]+dp[x−1][y−ax]となる。ここで、N番目のカードとi番目のカードを入れ替えて同じDPをしたものをdpiとする。dpiについても同じ漸化式が成立し
dpi[N][y]=dpi[N−1][y]+dpi[N−1][y−ai]順番を入れ替えてもN枚処理すれば同じ結果になるはずだからdp[N][y]=dpi[N][y]が成り立つ。したがって
dp[N][y]=dpi[N−1][y]+dpi[N−1][y−ai]整理して
dpi[N−1][y]=dp[N][y]−dpi[N−1][y−ai]という漸化式が立つ。i番目のカードを除いたとき和がy以上になるような集合の総数をf[i][y]とするとf[i][y]=dpi[N−1][y]だから
f[i][y]=dp[N][y]−f[i][y−ai]dpを求めた後、さらにfについてDPできる。
カードiが不必要
⇔ カードiを含む和がK以上になるような集合と、カードiを含まない和がK以上になるような集合が一対一対応する
⇔f[i][K]=dp[N][K]−f[i][K]
だから、この等式が成立するようなiを数えればよい。
dpの値は64ビットに収まらないので、適当にmodをとって扱う。弱衝突耐性があれば十分なので32ビットでよさそう。だめなら複数modを組み合わせればよい。
戻すDPを覚えるために復習。
ARC 028 D - 注文の多い高橋商店
とりあえず「簡単すぎる」ほうの問題を解く。dp[x][y]:=商品を最初のx種類からちょうどy個選ぶ方法の総数、とする。dp[x][y]=d[x−1][y]+dp[x−1][y−1]+...+dp[x−1][y−ax]と遷移できるが、これでは間に合わないので累積和を考える。つまりDP[x][y]:=商品を最初のx種類からy個以下選ぶ方法の総数としたとき、
DP[x][y]=dp[x][0]+dp[x][1]+...+dp[x][y] DP[x][y]=DP[x][y−1]+dp[x][y] dp[x][y]=DP[x−1][y]−DP[x−1][y−ax−1]が成り立つので、
DP[x][y]=DP[x][y−1]+DP[x−1][y]−DP[x−1][y−ax−1]というO(1)の遷移を導ける。ここからyを一個ずらした漸化式DP[x][y−1]=DP[x][y−2]+DP[x−1][y−1]−DP[x−1][y−ax−2]を引くと、dpについても同じ形の漸化式が得られる:
dp[x][y]=dp[x][y−1]+dp[x−1][y]−dp[x−1][y−ax−1]これで簡単なほうの問題が解けた。
さて、ここでN番目の商品とi番目の商品を入れ替えて同じDPをすることを考える。この配列をdpiとするとき、dpiはdpと同じ遷移で求まり、しかもN種類の商品を処理すると同じ値になるはずである。つまり、任意の個数yについてdp[N][y]=dpi[N][y]が成り立つ。したがって
dpi[N][y]=dpi[N][y−1]+dpi[N−1][y]−dpi[N−1][y−ai−1]という等式から
dp[N][y]=dp[N][y−1]+dpi[N−1][y]−dpi[N−1][y−ai−1]が導け、整理すると
dpi[N−1][y]=dp[N][y]−dp[N][y−1]+dpi[N−1][y−ai−1]となる。i番目の商品を除いた場合に全体でちょうどy個の商品を選ぶ方法の総数をf[i][y]とするとき、f[i][y]=dpi[N−1][y]であるから、
f[i][y]=dp[N][y]−dp[N][y−1]+f[i][y−ai−1]となり、fについてDPすればクエリにO(1)で答えられることになる。O(NM+Q)。
戻すDPというらしい。
2020年1月20日月曜日
EDPC X - Tower
問題文と制約を見てO(Nmaxsi)で解くしかないという気持ちになる。順番が固定されていないが、固定されていないと難しすぎるのでその方法を探す。
丈夫さについても重さについても、大きいブロックは下に置いたほうが良いはずなので、まずはsi+wiで降順にソートするという案を思いつく。実際、それでよいことが証明できる。有効なタワーが与えられたとし、丈夫さと重さが下のブロックから(s1′,w1′),(s2′,w2′),...,(sk′,wk′)になっているとする。隣り合うブロックでsi′+wi′≤si+1′+wi+1′が成り立つペアがあったとき、ブロックiとブロックi+1を入れ替えても有効なタワーのままであることが示せれば十分である。
ブロックiとi+1を入れ替えたとき、1,...,i−1番目とi+2,...,k番目のブロックにかかる重さは変わらず、i番目だったブロックにかかる重さは減るので、i+1番目だったブロックについてだけ考えればよい。このブロックに以前掛かっていた重さをGとすると、新しい配置で掛かる重さはG+wi′になる。ところでi番目だったブロックは丈夫さsi′で重さG+wi+1′に耐えていたのでsi′−wi+1′≥Gが成り立つ。si′+wi′≤si+1′+wi+1′からsi+1′−wi′≥si′−wi+1′≥Gが従い、丈夫さsi+1′でG+wi′に耐えられることがわかる。従って、ブロックiとi+1を入れ替えてもタワーは有効である。□
si+wiの降順に並んだタワーだけ考えればよいことがわかったので、あとは端からDPすればよい。dp[x][y]:= ブロック1,2,...,xから作れて、耐えられる重さがyであるようなタワーの価値の最大値、とする。
dp[x+1][min(y−wx+1,sx+1)]←max(∗,dp[x][y]+vx+1) dp[x+1][y]←max(∗,dp[x][y])と遷移すればよい。(∗は左辺)
2020年1月16日木曜日
2020年1月15日水曜日
第一回 アルゴリズム実技検定
リアルタイム受験した。3時間58分で全完。普段のABCよりさらに典型寄りという感じがした。
F
ソートするだけなのだが、計算量がすぐにはわからなかった。例えば単語がk個あるとしてヒープソートすると、新しい単語wiをヒープに挿入するたびに最悪O(logk)回の単語比較が必要で、比較にかかる時間は自身の長さで抑えられるから全体でO((∣w1∣+...+∣wk∣)logk)⊆O(NlogN)とわかる。最悪計算量がO(NlogN)であるような比較ソート一般でもそうなるとは思うけど、よくわからない。
H
平衡二分木でシミュレーションできるのでコンテスト中はそうした。
コンテストが終わったあと考えていたが、奇数番目カードの最小値、全体の最小値、奇数番目カードのオフセット、全体のオフセットの4つを保持すれば配列だけでシミュレーションできる。
I
ABC 142 - Eとだいたい同じ問題。ゼータ変換から部分集合に関するDP(O(3N+M))をやろうとして、これは普通のbitDP (O(2NM))で解けることを思い出した。
J
任意の始点から与えられた3点に向かう最小費用流を求めるイメージが浮かんだが、それはなさそう。→そもそも、フローが流れる辺が重複した時にコストが2回数えられるのはまずいのでは→いや、その時は別の始点から重複なしで流したのと同じ形になっているので、最小値には影響しないはず→だったら各点から3点への最短距離を求めれば十分では?→全点からダイクストラ。
よく考えると、全点からダイクストラの代わりに3点からダイクストラで十分だった。
N
各l,rについて、[l−C,l]あるいは[r,r+C]を空ける場合のコストを求めて比較すればよい。累積和でできるけど、これも遅延伝搬付き平衡二分木で楽をした。
O
f(x):=目xを出したあとの操作回数の期待値、として漸化式を書き出したあと、どう更新するか迷った。xが最大のときf(x)=0で、xが小さくなるにつれて有効な項が単調に増えていくので、降順に更新していくのがよさそう。特にデータ構造は必要ないのだが、なぜかBITが必要だと思ってしまった。
問題が公開されたのでもう一回解きなおしたら、全体的にかなり手間取った。当日は運がよかっただけかもしれない。
2020年1月14日火曜日
CodeChef January Challenge 2020 - Chefina and Prefix Suffix Sums
0+sufN=pre1+sufN−1=pre2+sufN−2=...=preN+0であることに注目する。この等式より、pren=sufnは入力全体の和/(N+1)と一致しなければならない。この数が入力の中に2つなければ答えは0である。
さて、この2数を除いた2(N−1)個の数の中から残りを埋めることになる。まず、ai+bi=Sになるような数の組み合わせを入力から探す。これらを
{a1,b1},{a2,b2},...,{ak,bk}とするとき、すべてのpについてapとbpは入力の中に必ず同数存在しなければならない。逆に同数あれば、prei=apのときsufN−i=bp(prei=bpのときsufN−i=ap)のように埋めていけば有効な列ができることになる。
{a1,b1},{a2,b2},...,{ak,bk}がそれぞれc1,...,ckペアあるとわかったら、あとは{a1,b1}から順にN−1個の位置を埋めていけばよい。つまり、
(c1N−1)2c1⋅(c2N−1−c1)2c2⋅...⋅(ckN−1−c1−...−ck−1)2ckが答えになる。N−1箇所の空きからc1個選んで、さらにa1とb1のどちらを置くか選んで…… というイメージ。ai=biのケースは2掛けがないので注意すること。
冒頭の事実に気づくのになんと二日かかってしまった。
CodeChef January Challenge 2020 - English
まず、接頭辞と接尾辞を両方考慮するという設定は、接頭辞だけの設定に直せる。アルファベットが26×26種類あることにして、単語とその反転を合わせて扱えばよい。例えばpoemなら(p,m)(o,e)(e,o)(m,p)という列だと思えばよい。
さて、本題は重みつきマッチングだが、一般のマッチングアルゴリズムでは無理なのでgreedyを探す。まず思いつくのはスコアの高いペアからマッチングしていくことで、実はこれでよいことがわかる:
単語x,yの最長共通接頭辞の長さをf(x,y)とする。また、4つの単語a,b,c,dが与えられたとし、fが最大になる組み合わせが{a,b}だとする。このとき、接頭辞の性質により
- f(c,d)≥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(a,b)2+f(c,d)2≥f(a,c)2+f(b,d)2が成り立つので{a,b},{c,d}と組み合わせるのがもっともスコアが高くなる。
したがって、マッチングとそれに含まれる2つのペア{u,v},{x,y}が与えられた時、4点の中でもっともスコアが高くなる辺を優先してマッチングをつなぎかえても全体のスコアは下がらない。このことより、最大スコアを達成するマッチングは上述のgreedyで得られることが示された。□
実装をどうするかで迷ったが、Trieにすべての単語を登録した上で、DFSして深いものから順にマッチングを決めていった。
2020年1月11日土曜日
第6回 ドワンゴからの挑戦状 予選 B - Fusing Slimes
求めるのは(N−1)!通りの移動に関する距離の総和である。
f(p,q):=xpからxqへの移動による距離の総和、とする。この移動が起こるのは、スライムp+1,...,q−1が自由な順番で選ばれ、そのあとでpが選ばれ、さらにそのあとでqが選ばれるケースである。この選び方を数えると、N−1箇所の中からq−p+1箇所を選び、その中の始めのq−p−1箇所にスライムp+1,...,q−1を自由な順番で配置し、末尾の2箇所にスライムp,qを配置し、残ったN−1−(q−p+1)箇所に残りのスライムを自由に並べればよい。つまり、
f(p,q)=(q−p+1N−1)(q−p−1)!(N−1−(q−p+1))!×(xq−xp)=(q−p+1)(q−p)(N−1)!(xq−xp)ただし、q=Nの場合はスライムqが選ばれないので
f(p,q)=(N−pN−1)(N−p−1)!(N−1−(N−p))!×(xN−xp)=(q−p)(N−1)!(xN−xp)となる。fの総和を単純に計算すれば部分点が取れる。
上の式でxq−xpに掛かる係数は幅q−pのみによって決まっているので、同じ幅について(q=Nの場合に関して)整理してみる:
==p=1∑N−1q=p∑N−1f(p,q)/(N−1)!2⋅11(x2−x1)+...+2⋅11(xN−1−xN−2)+3⋅21(x3−x1)+...+3⋅21(xN−1−xN−3)+..+(N−1)⋅(N−2)1(xN−1−x1)2⋅11((x2+x3+...+xN−1)−(x1+x2+...+xN−2))+3⋅21((x3+x4+...+xN−1)−(x1+x2+...+xN−3))+...+(N−1)⋅(N−2)1(xN−1−x1)((N−1)!は共通なので除いて整理した。) これは累積和で処理できる形になっている。
満点に達するのに3時間かかった。解説の方針のほうが確かにずっと明快だけれど、上の整理くらいはさっさとできるようになりたい。
2020年1月9日木曜日
いろはちゃんコンテスト Day 2 J - ヌクレオチド
Nが偶数の場合を考える。左右のN/2文字をそれぞれL,RとするとRはLの反転になる。つまり、Lの中で転倒Ai>Aj,i<jが起こっているとき、Rの対応する位置ではAi′<Aj′となる。L,Rをあわせると、Ai=Ajであるi,j以外は1回ずつ転倒を数えていいことになる。つまり、Lに0がz個含まれるとすると、Ai=Aj=0なる組が(2z)個、Ai=Aj=1なる組が(2N/2−z)個含まれるので、Lの並べ方全体からこれらを引けば(L内とR内の)転倒の総数が(zN/2)−(2z)−(2N/2−z)と数えられる。あとはL内の1とR内の0による転倒を足して
(zN/2)−(2z)−(2N/2−z)+(n/2−z)z=KLに0がz個含まれるときにはこの等式が成立していなければならないことになる。式を整理すると2z2−Nz+K=0となって、この二次方程式の整数解は高々2つなので、2つの解について数列の総数を求めればよい: (z1N/2)+(z2N/2)
Nが奇数の場合についても、例えば中央が0と決めて同様の議論をすればよい。(対称性により、中央が1の場合も同数の列がある。)
2020年1月1日水曜日
JOI2014 春合宿 - 歴史の研究
オフラインなのでMo's algorithmが適用できる。つまり、平衡二分木(あるいはセグメント木+座標圧縮)などを用意して、区間[l,r]を[l,r+1]に伸ばすときはdp[Xr+1]+=Xr+1と更新して、[l,r]を[l,r−1]に縮めるときはdp[Xr]−=Xrとすればよい。[l,r]から[l−1,r]、[l,r]から[l+1,r]なども同様である。それぞれのクエリについてmaxdpが答えになっている。O(NQlogN)。
平衡二分木を使ったらTLEしてしばらく放置していたが、よく考えると全体のmaxにしか興味がないので(優先度の変更可能な)優先度付きキューを使えば十分なのを思い出した。えびちゃんさんの記事にやり方が載っているので、書いたら通った。
CADDi 2018 E - Negative Doubling
完成した数列はどこかで符号がマイナスからプラスに切り替わることに注目する。N=5なら、
- +++++
- −++++
- −−+++
- −−−++
- −−−−+
- −−−−−
の6通りがありえる。たとえばA3で符号が切り替わると決めてしまうと、あとはA3,A4,A5の好きな数に好きなだけ4を掛けて単調非減少にして、A2,A1にも4を掛けて単調非減少にして、最後にA2,A1に−2を掛ければ全体として単調非減少になっている。
そこで、f+(x):=Ax,Ax+1,...,ANを単調非減少にするための最小コスト、と定める。おそらくf+(N+1)=f+(N)=0からスタートしてx=N−1,N−2,..と順にf+(x)の値を求めていけそう。さらに、f−(x):=Ax,Ax−1,...,A1を単調非減少ににするための最小コスト、とすれば(f−(x)+x)+f+(x+1)の最小値が答えになる。(+xは−2を掛けるぶんのコスト。)
さて、f+を求める具体的な方法が問題になる。この手順が与えられれば、f−は同じ方法を逆順の配列に適用するだけでよい。先に述べた通り、f+(N+1)=0からスタートして降りていくことにする。とりあえず、正しくないがシンプルな方針から:
- Ax≤Ax+1ならf(x):=f(x+1)。
- Ax>Ax+1ならAx+1に何回か4を掛けて非減少になるようにする。つまり、Ax≤4kAx+1となるような最小のkを求めて、以降のAx+2,...,ANにも同じだけ4を掛ける。このとき、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)=2までは正しく求まるが、A1=3を処理する際に4を掛ける必要があるのはA2=1だけで、A3,A4はそのままでいいことを見落としていた。つまり、A2,A3の間にはすでに4倍の差があるので、一回分の4掛けに限ってはA2で吸収されてしまうことになる。一般に、Ax+1がAxの4k倍以上である時、k回ぶんの4掛けはAxに吸収されてAx+1以降には影響しない。このkをxの 容量 と呼ぶことにする。上の手順で降りていくとき、各Axについて(容量が正なら)(x,容量)のペアをスタックに積んでいくことにする。
- Ax≤Ax+1ならf(x):=f(x+1)。xの容量が正ならスタックに(x,容量)を積む。
- Ax>Ax+1ならAx+1に何回か4を掛けて非減少になるようにする。つまり、Ax≤4kAx+1となるような最小のkを求める。スタックの先頭が(y,容量)になっているとき、Ax+1,...,Ayにmin(容量,k)だけ4を掛ける。このとき、操作回数は2min(k,容量)(y−x)だけ増えるが、容量がkより小さい場合はさらにスタックをポップしながらk回の4掛けが終わるまで繰り返す。
各インデックスについて、スタックへのpushとpopは1回しか行われないので、スタック操作は全体でO(N)になっている。