geam1113’s diary

主にAtcoderのコンテストの備忘録を書きます。AtCoder緑と水色を行ったり来たりしています。

AtCoder Beginner Contest 251 参加記録

コンテスト中AC:A〜C,E

E - Takahashi and Animals

グラフの問題として考えていたので、言い換えておきます。

頂点0,...,N-1について、頂点iと頂点(i+1)\ mod\ Nの間には辺iがあり、そのコストはA_iです。
ここから、以下の条件を満たすように辺をいくつか削除します。

  • すべての頂点の次数が1以上

残った辺のコストの総和をスコアとする時、スコアの最小値を求めてください。

辺がある時とない時で状態が2通りしかないので、iの小さい方から順にdpができないか考えます。

dp[i][j]:=iの状態がjの時のスコアの最小値。但し、
j=0の時、辺iは削除されている
j=1の時、辺iは残っている

遷移について、辺i-1番目が決定しているとして、辺iを考えます。

  • iを残す場合
    i-1は削除されていても残っていてもよいです。従って、
    dp[i][1]\leftarrow min(dp[i-1][0],dp[i-1][1])+A_i

  • iを削除する場合
    i-1は残っている必要があります。そうしないと、頂点iの次数が0になります、従って、
    dp[i][1]\leftarrow dp[i-1][1]

このように遷移していけば解けるかのように思えますが、例えば、i=0の時の初期値を

dp[0][1]\leftarrow A_0
dp[0][0]\leftarrow 0

として、i=2,...,N-1と更新していくとi=N-1で困ったことになります。というのも、

N-2が存在する場合でも、辺0が存在しない場合には辺N-1を残さなければならず、前述の遷移通りいきません。

これを解決するためには辺0が残っているかどうかの情報があれば良いので、DPテーブルに追加します。

dp[i][j][k]:=iの状態がjであり、かつ、辺0の状態がkの時のスコアの最小値。但し、
j=0の時、辺iは削除されている
j=1の時、辺iは残っている
k=0の時、辺0は削除されている
k=1の時、辺0は残っている

遷移は、k=0,1の両方の場合が必要になるだけで、先程と同じです。

0を初期値とします。
dp[0][0][0] \leftarrow 0
dp[0][1][1] \leftarrow A_0
dp[0][0][1] \leftarrow \infty
dp[0][1][0] \leftarrow \infty
としておきます。(dp[0][0][1],dp[0][1][0]は矛盾しており、あり得ない状況です。)

このDPを1,...,N-2まで順に更新します。
N-1の状態は、辺0,N-2の状態に従います。

  1. 辺のいずれか一方が削除されている場合
    頂点0,N-2の少なくとも一方は次数0なので、辺N-1は残す必要があります。よって、この時は、
    x_1 = min(dp[N-2][0][0],dp[N-2][0][1],dp[N-2][1][0])+A_i

  2. 辺の両方がある場合
    頂点0,N-2の両方とも次数が1なので、辺N-1は不要です。よって、この時は、
    x_2 = dp[N-2][1][1]

最終的な答えは、min(x_1,x_2)です。
提出コード

ABC250E Prefix Equality(ABC250 参加記録)

コンテスト中AC:A〜D
Eはコンテスト後に解説を読み、自分がコンテスト中に考えていた発想で問題なさそうだったので、その方針でなんとかACできました。
E問題について書いたので、記事のタイトルはEをメインにしました。

E - Prefix Equality

自分の実装

こちらの解説と多分同じ考え方だと思います。(多分)

方針

大雑把なイメージです。
(a_1,...,a_i)から得られる集合をS^a_iとします。同様に、(b_1,...,b_j)から得られる集合をS^b_iとします。

S^a_iと等しいS^b_jが存在すると仮定し、その中で最小のjlとします。

j=l,l+1,...と増加させ、b_jを追加していくと、そのうち集合として等しくなくなります(但し、便宜上b_{N+1}Aに含まれない値があるものとします。)。この時のjrとします。

この時、S^a_iに対しては、l\leq j\lt rを満たすS^b_jが全て等しくなります。

よって、各iについて、j区間を前計算しておけば、各クエリにO(1)で答えられます。

前計算の計算量

愚直に各iについて、j区間を求めようとすると、少なくともO(N^2)くらいにはなってしまいます。

しかし、実はi=1,2,..と探索していくと、jの探索範囲も増加するだけになり、結果としてO(N)くらいになります。
具体例と共になぜそうなるか書いていきたいと思います。

前計算の処理

以下の数列を例にします。
A=\{1,2,1,5,2,3\}
B=\{2,1,2,1,3,5,-1\} (-1は番兵)

以下の集合を定義します。

S_A : S^a_iに含まれ、S^b_{j-1}ではまだ見つかっていない値の集合
S_{AB} : S^a_iS^b_{j-1}に共通して存在する値の集合

i=1,2,...,Nについて、Aの先頭i項とBの先頭j項が等しくなるようなj区間を求めます。
初めj=1としておきます。

(1) S_Aa_1=1を追加し、これに対応するj区間を求めます。
b_1 = 2であり、S_Aに一致する値がないので、これに一致するj区間は存在しません。

(2) S_Aa_2を追加します。S_A=\{1,2\}です。
b_1 = 2なので、S_Aから2を削除して、S_{AB}に追加します。b_2=1も同様です。

ここで、S_Aが空になりました。これは、S^a_2 = S^b_2となったことを指します。よって、i=2のときに集合が等しくなる時の下限はj=2です。 下限が見つかったため、上限を調べます。

現在、S_{AB}=\{1,2\}です。
b_3=2,\ b_4=1S_{AB}に含まれるので、S^a_2 = S^b_2  = S^b_3 = S^b_4となります。
次のb_5 = 3S_{AB}に含まれないので、この時点で、S^a_2 \neq S^b_5となります。従って、半開区間[2,5)i=2の時に集合として等しくなるj区間です。

(3) a_3=1S_{AB}に含まれ、更にS_Aが空です。よって、S^a_3 は一個前のS^a_2と等しく、かつ、S^a_2にはj区間が存在していることがわかります。

この場合、S^a_3 = S^a_2であることから、i=3についてのj区間i=2の時と同じになります。

(4) a_4=5S_Aに追加します。この時、新たにj区間を再探索しますが、j=5からの探索として良いです。
なぜなら、b_1,b_2,b_3,b_4a_1,a_2,a_3に含まれるため、S^a_4を集合として等しくするために必要になるからです。また、b_5a_1,a_2,a_3に含まれない最初の値なので、ここから探索を開始する必要があります。

j区間の探索をしますが、b_5=3で、これはS_Aに含まれません。よって、i=4と等しいj区間は存在しません。

(5) a_5=2S_{AB}に存在するので、既にS^a_4,S^b_4の両方に共通して存在しています。そのため、S_Aには追加しません。
j区間の探索をしますが、b_5=3で、これはS_Aに含まれません。よって、i=5と等しいj区間は存在しません。

(6) a_6=3S_Aに追加し、S_A=\{3,5\}となります。

j区間を探索すると、b_5 =3,\ b_6=5と進めると、両方S_Aに含まれるので、S_Aから削除し、S_{AB}に追加します。j=6の時点でS_Aが空になったので、j=6が、S^a_6と等しくなる下限です。
上限ですが、番兵として入れてあるb_7=-1S_{AB}に含まれないので、i=6の時は、[6,7)が該当の区間になります。

以上で、全てのiに対するj区間が求まりました。

例で示したように、jは常に、Ai項目までに含まれない最初の値を指すようにしておきます。
すると、i+1目項までと等しい区間を探す際にjから開始すればよくなります。
なぜなら、S^a_{i+1}と等しい区間を探す際に、S^a_{i+1}S^a_{i}を部分集合として含むため、少なくともS^a_{i}と共通な値のみを含むS^b_{j-1}が必要となるからです。
これで、jは増加させるだけでよいことがわかりました。

提出コード

Zobrist Hash

公式解説にあったので解いてみました。

整数xを乱数に変換します。これをR_xとします。

集合S=\{a_1,...,a_m\}ハッシュ値R_{a_1}\oplus...\oplus R_{a_m}と定義します。
また、数列A,Bi項目までからなる集合から計算されるハッシュ値をそれぞれZ^A_i,\ Z^B_iとします。

するとi番目のクエリは、

  • Z^A_{x_i} =\ Z^B_{y_i}ならyes、そうでなければNo

と判定できます。

Z^A_i,Z^B_iの計算方法については、累積的にXOR和をとっていけばよいのですが、一点注意が必要で、同じ値を2回以上XORを取らないようにします。(偶数回とると、0になるため)

Bonusの問題は、単純に区間のXORをとっても上記の問題を解決できないので、わかりませんでした。

ハッシュの衝突に関してはyoutubeの公式解説では、64bit整数なら\frac{1}{2^{64}}くらいになるそうです。

なお、実装では 、 http://vivi.dyndns.org/tech/cpp/random.html

↑を参考に64bitのメルセンヌ・ツイスタに乱数をseed値として与えて生成しました。

提出コード

ARC139 参加記録

コンテスト中AC:A

A - Trailing Zeros

説明のため、二進数10桁しかないと仮定します。

例えば、T_i = 3の時、??????1000となり、?の部分が未確定です。
この時、??????の部分は、000000〜111111があり得ます。

000000, 000001, ...と増加させていくと、ある境でA_{i-1}\lt A_iとなり、以降全てこれが成立します。
よって、この境界は二分探索可能です。

最大何桁目まで考慮するかですが、問題文にも特に言及はなかったので64bit整数に収まるだろうと推測し、60桁としました。
ACできたので、問題なかったようです。

提出コード

コンテスト後に最大桁数について考えてみました。
A_Nが最大となるのは、N=10^5かつ、T_iがいずれも40の場合だと思います。

100...0が0〜41桁目までありますが、一旦無視して42桁目以降を考えます。

Aの42桁目以降は、{00...001, 00...010, 00...011,...}という感じで+1ずつ増やしていけばA_Nが最小となるので、A_Nの42桁目以降は10^5-1の二進数表記となっているはずです。

従って、A_Nの最大は、
99999の二進数表記 11000011010011111の17桁

10000000000000000000000000000000000000000の41桁

を連結させた数になります。

従って、58桁が考慮すべき最大桁数となると思います。

ABC249 参加記録

コンテスト中AC:A〜D

D - Index Trio

 \frac{A_i}{A_j} =A_kを変形すると、A_i = A_jA_kとなります。
A_iを固定すると、A_j,A_kA_iの約数の組に限定されます。
よって、A_1,...,A_Nについて、1\leq d \leq \lfloor \sqrt{A_i} \rfloorである約数dについてのみ調べれば良く、各A_iについて、O(\sqrt{A_i})で全探索できます。

計算量は、Aの最大値をA_{max} として、O(N\sqrt{A_{max}})となり、間に合うか微妙なところですが大丈夫でした。

A_i=d\times \frac{A_i}{d}となる組が何通りあるかですが、
cnt_{x}Aに出現するxの個数とすると、

cnt_{d} \times cnt_{ \frac{A_i}{d}}

となります。
これは、A_j = dとなる全てのA_jに対して、A_k =  \frac{A_i}{d}となるA_kと組を作ることができるためです。

1\leq d \leq \lfloor \sqrt{A_i} \rfloorであるdについて組を計算するわけですが、 d\neq  \frac{A_i}{d}である場合、組の順序を入れ替えると別の組になるので、2倍します。
逆に、d =  \frac{A_i}{d}の場合は入れ替えてできる組は同じなので、2倍しません。

提出コード Submission #31196140 - Monoxer Programming Contest 2022(AtCoder Beginner Contest 249)

なお、計算量を落とすことが可能です。
1,2,...,A_{max}の全てについて、エラトステネスの篩と同じ方法で、試し割り不要で約数が記録された二次元配列を得ることが可能です。
計算量はO(A_{max}logA_{max})となり、二次元配列のサイズも同様にA_{max}logA_{max}となると考えられます。

組の計算にかかる計算量ですが、

blog.hamayanhamayan.com

上記に高度合成数の言及がありますが、ある整数X以下の約数の個数が最大となるのが高度合成数です。
A_{max}が全て2\times 10^5以下の高度合成数Kであった場合が最悪となり、O(NK)です。

高度合成数の一覧 (10^18 以下) | アルゴ式

上記によれば2\times 10^5以下の高度合成数の約数の個数は160個なので、問題ないです。

Submission #31257209 - Monoxer Programming Contest 2022(AtCoder Beginner Contest 249)

ABC248 参加記録

コンテスト中AC:A〜D

D - Range Count Query

ウェーブレット行列が使えます。
ウェーブレット行列には以下の関数があります。

計算量は、今回の場合Aの要素の最大値A_{max}をとって、O(logA_{max})です。

各クエリは、quantile(L,R+1,X,X+1)とすることで答えられます。
よって、すべてのクエリをO(QlogA_{max})で計算できます。

ウェーブレット行列の構築にO(NlogA_{max})かかるので、全体でO((N+Q)logA_{max})です。
提出コード

E - K-colinear Line

K=1の時は無限に存在するので、"Infinity"です。

そうでない時、2点を決めれば直線を一意に定められるので2点を全探索します。

悩んだのが、同じ直線を2回以上使わないようにする処理です。

色々調べたことを書いておきます。

まず、2点(x_i,y_i),(x_j,y_j)を通る直線の式はググりました。

二点を通る直線の方程式の3タイプ | 高校数学の美しい物語

y-y_i = \frac{y_j - y_i}{x_j - x_i}(x-x_i)

ax+by+c=0(a,b,c)で管理

式変形すると、

-(y_j  - y_i)x+(x_j - x_i)y + y_i(x_j - y_i) - x_1(y_j - y_i)=0

なので、

a = -(y_j - y_i)
b = x_j - x_i
c = y_i(x_j - x_i) - x_i(y_j - y_i)

であり、これを管理します。

↓のtwitterで言及されていましたが、一意性を保つために正規化する必要があります。

熨斗袋 on Twitter: "格子点を通る直線は ax + by = c の形式で管理するのが楽なんじゃないかなと思う。"

熨斗袋 on Twitter: "gcd で割ったあと、min {(a, b, c), (-a, -b, -c)} で正規化"

  1. gcd(a,b,c)=1にする
    以下の2つは同じ式です。
    x+2y-3=0
    2x+4y-6=0
    これを同一視するため、g=gcd(a,b,c)として、ga,b,cを割ります。

  2. 符号を揃える
    以下の2つは同じ式です。
    x+2y-3=0
    -x-2y+3=0
    これを同一視するため、(表現が正しいかわかりませんが、)符号を揃えます。
    先程のツイートで言うところの、min({a,b,c}, {-a,-b,-c})を採用するのと同じだと思います。

下記実装では、構造体を作ってみました。
提出コード

y=\frac{d}{e}x+\frac{f}{g}(d,e,f,g)で管理

d = y_j - y_i
e = g = x_j - x_i
 f =y_1(x_j - x_i) - x_1(y_j-y_i)

となります。小数での管理は誤差が心配なので、分数で管理します。

分数\frac{p}{q}でも気をつける点があります。

  • gcd(p,q)=1にする。すなわち、gcd(p,q)p,qを割る
  • 符号を揃える。(正,正)なら(負,負)に、(正,負)にする。(これもmin({p,q},{-p,-q})で良さそうです)

また、この実装では、e=g=0のパターン、すなわち、y軸に平行な直線の場合は例外処理が必要です。
x = x_iとして、y軸に平行な直線のx座標を別に管理しておけば良いです。

これも構造体を作ってみました。

提出コード

あと、公式解説と他の方の実装についても書いておきます。

探索済みの点対を管理

公式解説の方法です。

P_i = (X_i , Y_i)とします。

flag[i][j]を点iと点jが探索済みならtrue、そうでなければfalseとします。
初め、全てfalseにします。

ある同一直線上にある全ての点の集合を、Sとします。Sに含まれるすべての点対(P_i,P_j)に対して、
flag[i][j] = true
とします。
こうすることで、全探索の際にflag[i][j]=falseである点対だけ直線を求めればよいことになります。

基準となる2点を管理

他の方の実装を参考にした時によく見られました。

直線は2点によって一意に定めることができるので、先程の集合Sを何らかの基準でソートして、小さい方の2点を記録すること等で管理可能です。

点が直線上にあるかの判定

直線上であるかどうかの判定は、直線ax+by+c=0の式に代入していましたが、外積でいけるようです。

ARC138 参加記録

コンテスト中AC:A

A - Larger Score

一応、考察した内容ですが、あまり自信はないです。

K番目までのAから順番を保ったまま抜き出した部分列をx=\{x_1,\cdots , x_m\}、同様にK+1番目以降の要素から得られる部分列をy=\{y_1,\cdots , y_m\}とします。

また、任意のx_iK+1番目以降に移動させ、任意のy_jK番目以内に移動させる操作を入れ替えと呼ぶことにします。

入れ替えに必要な操作回数

x_iy_jの入れ替えに必要な最小の操作回数は、x_iK番目に移動させた後、y_jK番目に移動させればよいので、x_iA_sy_jA_tとすると、K-s+t-K=t-s回となります。

複数個入れ替える場合に必要な操作回数

例えば、i'\lt iであるx_{i'},x_iと、j\lt j'であるy_j , y_{j'}を入れ替える場合を考えます。
この時、x_iy_jを入れ替えてから、x_{i'}y_{j'}を入れ替える必要があります。つまり、K番目に近いもの同士から順に入れ替えます。

そうでない場合、操作回数はこれより増えます。
なぜなら、例えば、x_{i'}を先にK番目に移動させると、x_iは1つ前に出るため、必要な操作回数が1回増えます。

問題の条件を満たすのに必要な操作回数

まず、明らかにx_i \lt y_jを満たすもの1組を入れ替えると達成可能です。

では、複数個入れ替えて最適になる場合があるか考えます。
今、K以下とK+1以上からm個選び、それらが、以下の条件を満たすとします。

x_1+\cdots +x_m \lt y_1+\cdots +y_m

ここで、x_i \geq y_jとなる組を取り除いても、条件は満たされます。

取り除けるだけ取り除くと、結局、任意のx_iy_jについてx_i \lt y_jが成立します。

取り除いた後の個数をm'すると、前述したとおり、x_{m'},x_{m'-1},\cdots , 1y_1,y_2,\cdots ,y_{m'}を順に入れ替えしていくのが最適です。
ここで、x_{m'}y_1を交換した時点で問題文の条件を満たすので、結局1個だけの交換を考慮するだけでよくなります。

また、x_{m'} = A_s,\ y_1 = A_tであったとします。この時、t'\lt tかつA_s \gt A_{t'}を満たすようなA_{t'}A_tの代わりにすると操作回数がより少なくなります。

結局、必要な最小回数の求め方は、i=1,2,\cdots  ,Kについて、A_iA_jを入れ替える際に必要な交換回数を求め、その中の最小が答えとなります。但し、jj\geq K+1かつA_j \gt A_iを満たす中で最小のものです。

jは、座標圧縮して、Segment Treeで計算しました。
提出コード

ABC243G Sqrt

自力AC

解をf(X)とします。

まず、\sqrt{x}以下の最大の整数は二分探索により、O(logx)で得られます。ここでは、sqrt(x)とします。

X\leq 10^6くらいの場合

dp[i]:=iが与えられた時に生成可能な数列の種類数

と定義すると、

dp[i]\leftarrow dp[1]+dp[2]+\cdots + dp[sqrt(i)]と求められます。

ここで、dpの累積和S_{dp}をとっておけば、

dp[i]\leftarrow S_{dp}[sqrt(i)]

と求められるので、sqrt(x)の計算も含め、小さい方からDPテーブルをO(XlogX)で計算できます。
なお、数列の項が1になった時点で1が延々と続くだけなので、初期値はdp[1] \leftarrow 1です。

この時の解は、f(X)=dp[X]です。

X\leq 10^{12}くらいの場合

f(X)=S_{dp}[sqrt(X)]となります。
前述した通り、10^6までのDPテーブルは計算できるので、解を得られます。

X\leq 9\times 10^{18}の場合

  • f(X)=dp[1]+dp[2]+\cdots + dp[sqrt(i)]

であり、

  • dp[i]=S_{dp}[sqrt(i)]

であるので、

  • f(X) = S_{dp}[sqrt(1)]+S_{dp}[sqrt(2)]+\cdots + S_{dp}[sqrt(sqrt(X))]

とおけます。
これより、sqrt(x)yとなる個数をg(y)とし、k=sqrt(sqrt(X))とすると、

f(X)=S_{dp}[1]\times g(1)+\cdots +S_{dp}[k-1]\times g(k-1)+S_{dp}[k]\times Z

とできます。
Zsqrt(X)以下でsqrt(x)kとなるものの数です。

g(x)は、x^2 \leq i \lt  (x+1)^2を満たすiの個数として求められます。

dp_2[i] = S_{dp}[i]\times g(i)

とすれば、O(N)でDPテーブルを計算できます。

また、S_{dp_2}dp_2の累積和とすると、

f(X)=S_{dp_2}[k-1]+S_{dp}[k]\times Z

とおけます。

後は、Zの求め方ですが、

Z=sqrt(X) - k^2 +1

で求められます。

提出コード