/ 最近 .rdf 追記 編集 設定 本棚

log[20200628] AtCoder Beginner Contest 172D 問題 Sum of Divisors



20200628()

最終更: 2020-07-05T23:55+0900

[AtCoder] AtCoder Beginner Contest 172D 問題 Sum of Divisors

コンテト全体については順当に冴えない結果であったあまり書くことがないので1つだ

 D 問題への Ruby による提出(実行時間昇)

他が概ね 1000 ms ほどかけているところ1つだけおよそ半分の 515 ms で済ませている>提出 #14757268

ープでは他と同じ式を使ってるんだけど半分に割って足しているところが鮮や足し算の背後にある論理がわかりません

N/2+1 から N までの数は掛ける2をするだけで N を超えてしまうのでその数自身しか数える必要がないというあたりかなープの中の計算が必要ない

こんな手の込んだことをしていながら提出時刻も早くてRuby の中では5番目なんだよね一方の自分はC 問題で脳死の愚直手続きスクリトを書いていた>提出 #14743690脳死のまま清書>提出 #14788308ステトメトを減らそうとしてやりすぎた>提出 #14788890

 脳死といえば……

D 問題にも最初は脳死状態で挑んでいたこういうスクリ

  1. N 要素の配列を 0 で初期化する
  2. 1..N のそれぞれに対してそれ自身と倍数に対応する配列の要素を +1 する
  3. 最後にその配列を使って K×f(K) (K=1..N) を計算して合計する

でもサンプル3が親切にも N の上限値でこのやり方では時間がかかりすぎることに気付かせてくれたさもなくばずぼらと拙速の代償として TLE を1個拝領していたことだろう

 [AtCoder 参加感] 2020/06/27:ABC 172 | maspyHP

D 問題問題と格子点の関連がさっぱりわからなかったのだけどープでシミュレトしてるΣ計算に N の一般式を与えようとしたときにΣの中に整数除算があるから反比例のグラフと軸のあいだの格子点の数に興味があるの? その前にk*N/k*N/k を約分したり分解したりはできない?

 ABC172D - 西尾泰和のScrapbox

ところでこの縦に足す方法では半分から先はどうせ1つしかないのにループを回して一つずつ足してしまうここを斜めに足せばループは半分で済むしかしどうせ斜めに足すなら… 左上までしっかり斜めに足す。そうするとループの回数はルNのオーダーになる

画像が見えない(scrapboxCSS を切らないと読めない)斜めに足すがわからないN のオーダーになるとは他所でも読んだがわからなかった

k*(N/k)*(N/k)N/k が1になるものだけを特別扱いするのでなく2になるもの3になるもの5……で分けると定数係数としてΣの外に出せる(それとΣの区間も変化する)とかそういう話なんだろういやどう書いてあるかはざっと読んだんだけど読んだだけで解れば世話がないわけで……(あとでスクリトにして確かめよう)

 スクリトにして提出したら 997 msったものが 68 ms になった(比較のため Python だと 32 ms)

しかし明らかにもっとすっきり書く方法がありそうなんだよなっていうかそれはすでに Python スクリトとして示されてるんだけど理解できないのです。

自分がやったのはすでに書いた通りN/k が1になるものだけを特別扱いするのでなく2になるもの3になるもの5……で分けると定数係数としてΣの外に出せるということを利用してk=1..N のループについて前から計算すると同時にループの反対側にある N/k==k ()となるケースを計算して両端からループを進めようということ繰り返し回数は 1,2,3,...,N/3,N/2,N の半分になるはずなんだけど中間地点がどこにあるのか全長がいくつになるのかわかりません

でもまあN のオーダーになってるみたいだからN=a*a だとして1,2,...,a-1,a(=N/a),N/(a-1),...,N/2,N なんでしょう

 整数への切り下げがなければ

m=N/k; n=N/(k-1) なのを利用して 68 ms のスクリトのループの中の式を s+=N*(N+1)/2; s+=k*m*(m+1) と整理できそうなんだけどそうするとこれまたどこかで見たような式(と定数)でさらに整理できそうな雰囲気があるんだけどmn の関係は通分したり約分したりできる関係ではたぶんないんだよね(答えが合わないから)

 AtCoder Beginner Contest 172 D - Sum of Divisors - Crieit

図がわかりやすいーダーをちっとずつ改善していく構成が付いて行きやすいそして最後に見逃せないこれ>なおO(N^1/3)の方法もあるらしいで

格子点のやり方がこのオーダーらしいっぱり想像がつかない

ゃあねっかくリンクを張って紹介してくれた先(格子点の数え上げの高速化 - memo)を読みましょうよって話なんだけど高速化云々より前に格子点がどのように関わってくるのかがまず知りたいよね

 格子点の数え上げの高速化 - memo

まずここから(わかんない)

1 から n までの約数の個数の総(つまりy=n/x の第一象限内の格子点の個数2i=1nni-n2 などを用いて計算することが多く

  • y=n/xy=x の交点が (n,n)
  • y=n/xy=x を軸にして対称
  • x=1..n の範囲で y=n/xy=x のあいだにある格子点を数えて2倍する? (たぶん y=x 上の格子点を一度だけ数えるよう気を配るのが面倒くさ)
  • x=1..n の範囲で y=n/xx 軸のあいだにある格子点を数えて2倍して重複して数えた (1,1)から(n,n)を対角線とする正方形領域の格子点を引く
 「考え方
  • 曲線上にある格子点が列挙できたら
  • 隣り合う格子点間を結んで斜辺とする各台形内の格子点を計算で求めるだけでいい
  • 斜辺上にある格子点にだけ注意してね(傾きが既約分数の場合)
  • 最初のステップの列挙には SternBrocot tree が使えるよ
 「求め方
  • わかりませんトン法みたいな試行を繰り返すの? むしろユークリドの互除法?

 ープをまたいで式を整理した

それというのも Python の方には2桁msの提出が1ページ以上もあってーダーは変わらないしブレもあるだろうとはいえ28 ms32 ms のスクリトのあいだには明らかに式の複雑さに差がある

 Python によるすべての提出(実行時間昇)

実行時間昇順で並べたときだけ自分の 32 ms の提出がリトされない降順だったり提出時刻だったりでソトすれば現れる消えているときは代わりに他の人の 32 ms の提出が2回リトされている

32 ms の1つは自分のだが28 ms は例えばこれ>提出 #14788253平均タイムからして明らかに速い

未だ及ばずながらだいぶ迫ったのではない

 これで最後

最後に÷4するのにループの中で無駄に×4してるのが気になったので

これを最適化というのではないこの問題でしか意味のないループになった少なくとも自分は式の意味を途中からは理解していない

実は最終版の whileープの中身は一番最初の 997 ms の提出とそっくりになっているってきた

 Python のこの提出 #14841471

Ruby で書くとこんな感じ

N = gets.to_i
p (1..Math.sqrt(N)).sum{|k|
	n = N/k
	k*n*(n+1)-k*k*k
}

違いを見比べると -k3 がループの中にあるか外にあるかの差なんだろうWikipedia による と i=1nk3=n(n+1)22 らしい

k*[n*(n+1)-k*k] からは何か意味が読み取れそうな気がするね数学力があれば見えるんだろう数学力があれば意味を保ったまま易々とたどり着けるんだろう

 結局これでいい

ープ後のつじつま合わせの正体が -k3 だとわかったので……

N = gets.to_i
s,k,n = 0,1,N
while k<=n
	s += k*n*(n+1)
	k += 1
	n = N/k
end
s -= (k*(k-1)/2)**2
p s

 両辺の k は異なる右辺の k1,2,3,... の順で繰り返される k としてそれに対応して左辺を満たす kN,N-1,N-2 の順で発見される1対1対応ではない