/ 最近 .rdf 追記 設定 本棚

脳log[2023-08-09~]



2023年08月09日 (水) [AtCoder] 精進1。ABC127-F「Absolute Minima」(黄 diff)。サンプルの1を読めば f(x) を1回2回置き換えたときの答えの求め方がわかる。あとは3回4回のケースが想像できればいい。■提出 #44387658 (AC / 1233 Byte / 858 ms)。f(x) を更新するにあたり b は総和を覚えるだけでいい。a については中央値と中央値からの離れ具合の総和が知りたい。それは a のソート列と累積和があればわかる。おなじみ BIT で効率的に動的に管理する。■■■精進2。ABC130-F「Minimum Bounding Box」(黄 diff)。まず入力を絞る。d={R,L,U,D} それぞれについて X 座標の最大値最小値、Y 座標の最大値最小値にしか用がない。X 座標について考える。X 座標の最大値最小値の差が減るか一定か増えるかが切り替わるかもしれない時刻が4つだけある。右に移動する X 座標の最大値が左に移動する X 座標の最大値と交差するときが1つ。右に移動する X 座標の最大値が上下に移動する点の X 座標の最大値と交差するときが1つ。3つ目と4つ目は同様に左に移動する X 座標の最小値が~。Y 座標も同じく最大4つの時刻で差の増減トレンドが変化する。求めるものは差と差の積の最小値だけど、差の増減トレンドと積との関係はどうなっているだろうか。(差が減る)×(差が減る)=(積が減る)、増×増=増、減×増=?、増×減=?。書いててわからなくなった。差が増減する早さに秒速1と2の2種類ある。1種類なら平方数を最大として上に凸のグラフのある範囲を切り取った変化をすると思ったけど、2種類だとどうなるんだろう。下に凸になることがあると思ったから三分探索をしたけど、今になって三分探索ができる単純な形の変化をするとはわからなくなってしまった。■WA×1 のち AC (1480 Byte / 187 ms)。


2023年08月08日 (火) 悪名高きスワイプ広告を解析する - Qiita」■スワイプ広告というものは知らなかったけど、興味深い分析だった。で、Flash 広告を思い出した。あれはフォーカス(黄色い枠だった?)を得るとすべてのキー入力を呑み込むために Web ページの閲覧を阻害する異物であり嫌っていた。スワイプ広告も埋め込みスクリプトによる治外法権が認められている異物らしかった。再実装されたスワイプ処理がお粗末なのか悪辣なのか外からは何とも言えないけど、治外法権を認めていることがそもそも失策であり現状は予期された帰結だと思う。つくづく癌だよな。■■■思い込みで書いてる部分があった。スクリプトを誰が書いているかという部分。自分は広告の配信者だと思ったが掲載者であるかもしれず、元の記事では「あくまで推測だが、おそらくこの誤タップ広告システムでサービスを提供している会社が存在し、」と第三の存在が推測されている。


2023年08月07日 (月) [AtCoder][Ruby] 「AtCoderの新ジャッジ、Rubyの一部提出がメチャ遅い実行時間になっているのだけど require 'prime' があると遅くなるみたいだなー」 これを見てコードテストで require 'prime' とだけ実行してみたら 2525 ms かかって「マジかー」となったのだけど、その後は 55 ms 程度で落ち着いている。インタープリタ起動のオーバーヘッドがおよそ 40数 ms なので、require 'prime' にかけている時間は 10 ms 程度。まあ妥当。他にも require 'openssl' が数百 ms かかったのも確認したんだけど、次に確認したときは 55 ms になっていた。その次は 80 ms。require ひとつのために確率的に TLE になってペナルティを食らうのは嫌すぎる。■magurofly さんの全く同一ソースの2つの提出がこちら。#44334461 (1897 ms)、#44345492 (66 ms)。一度も呼び出されていないメソッドはコンパイルされないだろうから JIT ではないよね。じゃあ rubygems なのかと思うけど、require 'prime' にネットワークが関わる要素はないよね。ね? じゃあどこで? あるいはジャッジサーバーが?


2023年08月05日 (土) [AtCoder] 今日は第四回日本最強プログラマー学生選手権-予選-(AtCoder Beginner Contest 313)があった。オンサイトコンテストの予選だから問題の難化が予想されたけど、配点でもそれが裏付けられている。100-300-400-550-600-625-625-650 は A-C-D-E-F-Ex-Ex-Ex ってことですかそうですか。今日は 100 分5問の、普段よりビギナー向けのコンテストになりそうですね。以下 ABCE のふりかえりと D の精進。■A 問題「To Be Saikyo」。やることは簡単だけど制約とサンプルがいやらしいですね。ひっかかりません(N=1 のケース)。■B 問題「Who is Saikyo?」。愚直に再帰的に何人に勝っているか数えても許されると思ったけど TLE を出した。「全ての情報が正しくなるように、全ての相異なる 2 人組にどちらが強いかを割り当てる方法が少なくとも 1 通り存在する」という制約から推移律を破るような循環する入力は与えられないと思ったけど勘違いだろうか。あるいは単純に計算量が見積もれていないだけだろうか。N≦50 だから4乗でも通るはずだけど。TLE の原因がわからない。Ruby で最初に提出された #44259309 (kuma_rb さん) の着眼点がすごい。負けなかった人が一人だけいたならその人が最強だと。トーナメントではそういう話を聞いたことがあるけどね(「優勝者とは唯一負けなかった者のことである」)。■C 問題「Approximate Equalization 2」。これは B 問題より簡単だった。平均として考えられる2つの整数(切り捨てと切り上げ)を考えて、A 数列のすべての要素について下げるなら下げる量、上げるなら上げる量を数える。下げる量と上げる量の大きい方が答え。上げるのと下げるののうち操作回数が余った方は2つの平均値のあいだを移動するのに使われている。■D 問題は答えを求める手順がわからなくて飛ばした。もし答えが絶対にわからないときの答え方が用意されていたら、まんまと「わかりません」と答えて WA をもらっていたところだった。■E 問題「Duplicate」。そもそも長さ1に収束するというのがけっこうなレアケース。S="22" でも永遠に短くならない。先頭の文字はどうでもいい。2番目以降の文字が操作後の長さを決める。1なら現状維持(ということは操作により末尾の1文字分減る)。2以上なら伸びる。しかし2から9までの数が2から9までの数を増やして伸びることはない(そうしたら収束しない)。2から9までの数が末尾に至って消滅するまでに何個の1を生成するかを数える問題。右側にある文字数と同じ回数だけ、決まった長さ(1から8)だけ伸びる。右側にある文字数が知りたいので末尾から(ほとんど1の)長さであり操作回数を数える。■D 問題「Odd or Even」。コンテスト終了まで考えていた F 問題 Flip Machines が解けなかったので終了後に D 問題に戻ってきた。N 回のクエリで数列全体の偶奇(の連鎖)が決まるのはわかる。たとえば A[1..K] の偶奇がわかって A[1..K-1,K+1] の偶奇がわかると A[K] を基準にして A[K+1] の偶奇が同じか異なっているか決めることができる。同様にして A[K+2] 以降も決める。A[1] から A[K-1] の偶奇についてもうまいことやって決める。ここからが問題だった。クエリ回数は上限の N 回まで使い切っている。A[K] の偶奇を基準にして同じか異なっているかはすべて判明しているが、A[K] の偶奇を決めることができるのかどうか。たぶんここで K が奇数だということが効いてくるのかな。最初のクエリを思い出そう。A[1..K] の偶奇がわかっている。そして A[K] を基準にした仮の偶奇について、1から K までの範囲の偶奇はどうなっているだろうか。一致しているだろうか異なっているだろうか。もう解けた。■コンテスト成績証自分のすべての提出。D 問題も時間内に解きたかったけど、終了後に1時間かけているので惜しいところはなかった。それというのも人間ジャッジがときどきレスポンスの偶奇を間違えるからなのですね。解答のデバッグと並行していつの間にかジャッジのデバッグをさせられていたのでは時間もかかる。D 抜きでも青パフォが出たのは E 問題様様です。■F 問題について。N≦40 と制約はかなり控えめ。各マシンについてありうる未来はかなり限定されていると思った。操作しなければ2枚のカードについて {表表}。1回の操作で {表裏,裏表}、2回の操作で {表表,裏裏}、3回の操作で {表裏,裏表} これは1回の操作と同じ未来。それでどうする?


2023年08月02日 (水) [AtCoder] 精進。ARC010-C「積み上げパズル」(黄 diff)。色ボーナスもコンボボーナスも全色ボーナスもマイナスになりうるのがゲームのルールとして直感に反する。でも DP をするならそこは問題にならない。DP 配列を埋める初期値にだけ注意したらあとの遷移では比較して大きい方をとるだけ。どういう DP をするか。コンボボーナスを加算するために最後に積んだ色が何色かで M 通りに分類する。全色ボーナスを加算するためにこれまでにどの色を使ったかを 2^M 通りのビットフラグで分類する。M の上限が 10 なので組み合わせた状態数が最大で 10240 通りになる。遷移の回数 N が 5000 以下だからおおよそ 5000 万の処理量は Ruby には厳しいかと思ったけど、テストケースが制約の上限を攻めていないのか処理時間には余裕があった。定数倍2分の1のために必ず立っている1ビットを効率的に省略する方法を考えていたけど必要なかった。■WA×41WA×18WA×3 のち AC。■41→18 で解消したバグは、色ボーナスとして加算する値が落ちてきたブロックではなくすでに積まれている一番上のブロックの色によって決まっていたうっかりミス(21 行目)。■18→3 で解消したバグは、ブロックが1つも積まれていない状態(スコア0)が存在していなかったこと。■3→AC で解消したバグは、それまではちゃんと対策できていたのに他のバグを解消したときに不要になった気がして省略した処理がやっぱり必要だったために生じた新しいバグで、最初のブロックの扱いをどうするかに関わること。色数 M が1より大きいなら特別扱いは必要なくて、他の色の空の状態(スコア0)から遷移してくることでコンボボーナスなしの色ボーナスだけを加算することができる。しかし使われている色が1色だけのときに特別扱いなしでは最初のブロック(コンボボーナスなしの色ボーナスだけ)がうまく扱えていなかった。M+1 色目が追加できたらややこしいことは何もなかったけど、TLE が怖くて1ビットだって削りたかったときにそれは取れない選択肢。


2023年07月29日 (土) [AtCoder] 今日はユニークビジョンプログラミングコンテスト2023 夏 (AtCoder Beginner Contest 312)があった。以下 ABCDF のふりかえりと GE の精進を。■A 問題「Chord」。こういうのはコピペを活用してできるだけミスの混入を避けるべきなんだよね。そういう意味で自分の提出 #44032329 はコピペしたあとに手を動かしていて良くない。Shiro_S さんの提出 #44031960 を見習いたい。■B 問題「TaK Code」。すっかり実装が面倒くさい枠の B 問題。4×4×2=32 マスの黒白を愚直に確かめるだけ。そんなんでも添字の範囲を間違えたりしてたいへんなんですよ。■C 問題「Invisible Hand」。ぼんやりして全然頭がはたらかなかった(いつものことか?)。問題文を読むかぎり必ず答えが見つかるみたいな雰囲気を感じるけど、そうだとはわからない。目に付いた数字で二分探索をするもサンプルの2がありがたくも優しくそれではダメだと教えてくれる。A、B 数列の両端付近の値を候補に加えて二分探索で答えを探したけど WA だった。それならと各数字の前後を候補に加えて 3N+3M 個の数字から答えを探したら AC だったけど、あまりにひどい力業。TLE になっていたらお手上げだった。Ruby で2番目に早く提出された gmmtea さんの決定的な提出 #44046237 がすごい。探索などしていない。■D 問題「Count Bracket Sequences」。2乗が通る制約。(閉じていない)開き括弧の数ごとに場合の数を記録する DP をやってみたら DP 配列を shift したり unshift したりする面白い形の遷移になった。もっと効率的なやり方(※)がありそうではあるけど、制約で許されているので愚直に配列を操作した。※倍の長さの配列を用意して真ん中を暫定の先頭にする。実は Ruby のインタプリタが shift も unshift も勝手にうまくやってくれるので考える必要がない。■E 問題「Tangency of Cuboids」。幾何問題。心中リスクが高いので飛ばした。終了3分後の提出 #44081331 は TLE だった。■F 問題「Cans and Openers」。配点は E 問題と同じ。貪欲法でできるかなと考えてみたけど、缶切りの数に応じて答えがガタガタして無理そうだった。場合分けも、缶切り不要の缶が M 個に足りない場合や、缶切りがあっても缶切りが必要な缶が十分にない場合など、状況と状況の重ね合わせまで考えるとたいへん。2本の累積和からいい感じに M 個を取るという構図から ABC116-D Various Sushi を思い出していた。しかしその精進(20220609)は生きず。BIT を2本使って缶切りの数ごとに愚直に満足度の総和を求めた。Ruby によるすべての提出を見ると、貪欲っぽいもの、プライオリティキューを使うもの、DP らしいものなどいろいろ。log が付くデータ構造を使った自分の提出 #44067838 などは時間が余分にかかっていて、もうちょっと頭を使ってがんばりましょう、という感じ。■G 問題「Avoid Straight Line」。コンテスト終了後に本腰を入れて考えた。単純パスを構成しない3つ組とはどういうものだろうか。制約を見るとペアを考えることが許されていないので、ペアから LCA を求めて……という解法にはならないとわかる。それで、どういう3つ組? 根っこに向かって一直線の3つ組ではない。2頂点とその LCA から成る3つ組(ペアの場合もあるが無視する)ではない。根っこに向かう直線ペアと×××の組み合わせではない。……。部分木ごとに直線ペアの数と分岐するペアの数を記録して根っこに向かう DP をすれば求めたいもの(分岐するトリオの数。変数名 spt は split trio の略らしいです)が計算できそう。提出 #44086692 (AC / 488 Byte / 1741 ms)。サンプルの3が合わないときは3つの子から1つずつ選ぶ spt を数え忘れてるかもね(経験談)。Ruby による G 問題へのすべての提出を見ると自分の遅さが際立っている。なぜ?■コンテスト成績証自分のすべての提出。ABCDF の5完でまずまずの成績。(終了後でも) G 問題が解けて満足している。AtCoder Problems によると難易度勾配は F(水)<G(青)<E(黄) らしいけどね。たしかに、E 問題の TLE を解消する手立てがまだわからない。直方体の数が 10 万あるのに、あれとこれが接触している、これとそれが接触している、と個別にペアを数えてはいけないのかもしれない。だけど6面あって重複をカウントしないように何ができる?■■■G 問題。自分の提出が他と比べて桁違いに遅いのでちょっと考えてみた。前回は子(部分木)ごとにサイズと直線ペアの数を記録して求めるもの(分岐トリオ)を計算して足し合わせていた。分岐トリオを求める計算が余事象が関わっていてちょっと面倒くさい。今日は子(部分木)ごとにサイズと直線ペアと直線トリオを記録して、根っこで一括して余事象を求めるようにした(3つ組の選び方から直線トリオを引く)。提出 #44113272 (AC / 309 Byte / 602 ms)。桁は並んだけど 400 ms 台、500 ms 台に比べると遅い。メモリ消費が4倍くらいあるんだよね。根っこから再帰関数を呼び出すのでなく、葉から根へ積み上げるべきなのかもしれない。だけど記述のシンプルさにはあらがえない。簡潔に書けてると思うん。ゴルフしてるものにくらべるとまだ余計なことをしてる無駄な記述があるみたいだけど。■E 問題。座標空間が 100×100×100 でえらく狭いなと、ここに直に書き込んで何かできないかと考えてはいた。でも 100×100×100×N では TLE だしなーと。見落としていた重要な制約があった!!! 「直方体同士は体積が正の共通部分を持たない」 だったら N 個分書き込んでも総和は 100 万以下じゃん! 隣のマスを見たら隣接直方体がわかるやん! 提出 #44116267 (AC / 553 Byte / 729 ms)。■延長1日を含めてこれもう実質7完でいいんじゃね? やったぜ!(めでたい)■■■G 問題。現在見ている頂点を3つ組の真ん中の頂点だと考えて直線トリオを数える DP ができるらしい(最後に3つ組の全選び方から引く)。提出 #44139844 (AC / 303 Byte / 634 ms)。そうすると直線ペアの数を数える必要がなくなって、サイズと直線トリオの数だけを数えればよくなったけど、期待に反してわずかに遅くなった。400 ms、500 ms 台に入ると思ったのになぜ? 実は答えを合わせるのにすこし苦労して、現在見ている頂点を3つ組の真ん中に決め打つということは、他の2頂点を選んでくるのは子方向の部分木だけに限らず親方向からも選んでこられる(16 行目)。その点がこれまでの2つの解答と異なっていた(これまでは現在見ている頂点が LCA になるような3つ組を考えていたので問題が子孫方向の部分木に閉じていた)。■E 問題。たぶんだけど、サンプルにわかりやすーい図解が付随していたら当日の AC がもっと多かったと思う。問題文を読んでも全然具体的イメージが構築できなくて苦労したもんね。まず直方体の対角線って何だ、とか。それは長方形の対角線とは(必ず)違うと考えていいのか、と。不等号(X1<X2, Y1<Y2, Z1<Z2)に意味を持たせすぎないでもっと文章で説明してくれていいのよ。辺が軸に平行だということも、え、それってどういう、という疑問からなかなか先に進めなかった。脳内直方体は歪んでいた(それは直方体ではなくない? 菱餅? そんなつっこみも出てきやしなかった)。■■■F 問題。自分の提出は BIT を使ったせいで余分な log がついて時間がかかっていた。よく考えて整理した結果、2本の累積和から M 個を取ったあとで綱引きをするようにした。要するに尺取り。提出 #44215202 (AC / 619 Byte / 389 ms)。最速級まではいかないけど BIT 版より倍は早くなった。じっくり整理したので規則的で理解しやすいと思う。S2 に前加工を施すと k1x の補正が必要なくなるし、無意味に缶切りを取得する前に試行を中断できるけど、それは TODO。


2023年07月28日 (金) 初めて免許を取ったときの教習の一幕。S 字を通過していたら止められて、助手席側のドアを開けてみせて見てみろと。こんなにタイヤと縁石が近いと。はあ、なるほど。で、どうすると問われました。(ぎりぎりだけど当たってないので)行けるんなら行きます、と前進しようとしたら呆れられました。いやあ、だって、ねえ、ドアを開けて目視でセーフが確認できたんだから、行きますよねえ。熱烈指導(お説教)が始まりそうな気配だったので慌てて(ルームミラーと後方を確認して)バックして進路を修正しましたけども。前進しかできないわけではないことを証明しましたけども。■何の話かというと、「(ぎりぎりでも、忠告を無視するような形になっても)行けるんなら行きます」という選択をする傾向が他の場面でもあるかもなあと思ったからなのでした。


2023年07月26日 (水) 動悸という言葉について。辞書には「心臓の鼓動が平常より烈しいこと。また、その鼓動」「動悸がする」「動悸を打つ」などと書かれている。つまり動悸という言葉の中にすでに鼓動の早さ強さが含まれている。今日「動悸が早くなった」という用例を本で見かけた。珍しくはない使われ方だけどちょっとひっかかる。「鼓動が早くなった」とか、ちょっと古めかしい定型かもしれないけど「心臓が早鐘を打った」とかで良くない? でも、すでに動悸がある状態がさらに程度を増したというように読み取ることができなくはないかな、と思いもする。そのつもりで書かれただろうとは思わないけど。辞書に書かれているのは前例であり、現在の用例も前例のひとつになりうる。動悸という言葉が使いやすい形と意味を持っていないバグが是正されていく過程にも思える。どっちつかずの微妙なきもちにさせられる言葉。あるいは個人的な見当外れのこだわり。「HTTP プロトコル」の仲間に見えるんだよなあ。Horyuji Temple が仲間に加わるとむしろありの例なんだよなあ。


2023年07月25日 (火) [AtCoder] 精進1。ABC104-D「We Love ABC」(青 diff)。3つの要素があるときに真ん中に注目するという典型をこの前仕入れたのでその方向で考えてみた。各文字を見ていくとき、その文字が B である場合と、? を B にする場合に ABC 数を数えて足し合わせたい。前準備として左にある A の数、? の数、右にある C の数、? の数を知るために累積和を用意する。既存の A のうちのひとつを利用する場合には左にある ? に ABC のどれを割り当てても良い。? のうちのひとつを利用する場合には割り当て可能な ? の数が1つ減る。わかりにくかったのは、文字列の種類数と ABC 数としてカウントするための A の文字の選び方の区別。つまり、? への文字の割り当て方は 3.pow(? の数) 通りあって、それぞれの文字列に対して A の数だけ ABC 数を数えるための重みがあるということ。同一文字列を重複して数えてはいけない気がして ? への文字の割り当て方に制限を加えて順序よく数え上げようとしていたのだけど、全然いらない心配だった。ある A(もしくは A を割り当てた ?)に注目して ABC 数をカウントするときと別の A(もしくは ?)に注目して ABC 数をカウントするときに、それぞれに S 全体が同一の文字列になるものがあってもよい。■提出 #43942246 (AC / 451 Byte / 220 ms)。■■■精進2。ARC092-D「Two Sequences」(黄 diff)。XOR を考えるときに問題を桁ごとに分けるのは基本だけど、足し算がかかわると繰り上がりがやっかいだよね。ひとつの対処法を知っている。ARC158-C「All Pair Digit Sums」を解いていたときのこと。「kotatsugame さんの動画を見ていたら k 桁目を見ているときにその桁の数字と同時に 10**(k-1) で割った余りを持てばいいと教えてもらった。たとえば A={1234,9876} だとして 10^2 の位に注目しているとき、(2,34),(8,76) を処理対象にする。余りの部分は小数点以下の数字みたいなもの。言われたら、たしかにそれでできそうではあるけど、でも、そういう発想はたぶん待っていても出てこなかっただろうな」。そうすると数列をマスクしてソートして、注目しているビットが1になる境界、繰り上がって0に戻る境界、さらに繰り上がってきて1になる境界がこの順で見つかる。時間が厳しいので二分探索を使わずに尺取りでやる。これは2か月に渡って苦しめられた Pairs の教訓。■提出 #43950013 (TLE×4) のち 提出 #43950581 (AC / 515 Byte / 2106 ms)。正直初手で通ってほしかった。TLE を回避した手段はイテレータメソッドを while ループに書き換え、Array#map と Array#sort を破壊的メソッドに置き換えたこと。読みにくくなるだけでアルゴリズム的な改善はない。■Corvvs さんの提出 #3092072 を見ると添字を3つも管理していない。キャリーにだけ注目している。AB 変数の意味もわからない。むむむ。■1のビットの数の増分だけ見てるのかな。キャリーが起こると1が2個減って上の桁で1が1個増える。2個減るのは XOR を考える上で意味を持たないけど、1個増えるのは答えに影響する、みたいな? AB 変数がベースラインで、rmask 変数がキャリーによる変動、とか? rmask 変数の末尾に無条件で 0 の桁を追加してるのもそれっぽい。自分でコードにできるほどはっきりはわかんないけども。■ところで、自分が他人のコードを読むときって、完全に雰囲気で読んでいる。個別の式の意味を解釈するよりも、全体の形(ループとか)に注目している。雰囲気だから「みたいな?」みたいな感想になる。たとえばこのときも「面白いことを書いている人がいる。「ABC303-D この問題の正解者は実は間違っていた」。形式だけ見て気が付いたことを」。どういう値を触っているかだけ見てその値の意味を理解しないまま感想を書いている。細かい話は苦手なんだよ。確率・期待値・数え上げ問題の数合わせとかもー最悪。■提出 #43953782 (AC / 484 Byte / 1605 ms)。キャリーによる増分に注目するので間違いなかった。でも伏兵があった。ベースラインとなる繰り上がりを考慮しない和(それは XOR と等しい)の XOR を求めるところに罠があった。項数 N が偶数の時はきれいに打ち消し合って0になるのだね。これはきっちり式を整理して計算しないと気付けない。■提出 #43989881 (AC / 842 Byte / 1496 ms)。Array#sort(一般に NlogN)の隣で二分探索を尺取りにしてもそれは定数倍の改善だし、3つの尺取りが1つの尺取りになってもやっぱり定数倍の改善だけど、ソートを工夫するところまでやると log が落ちて Nlog(N)log(max A) が Nlog(max A) になるっぽい。「数列のソートに桁ごとのバケットソートを使用します。下位の桁から順に計算していけば基数ソートの動きそのままになるので、各桁で O(N) でソートが可能です」 ちょっとだけ早くなった。ちなみに、配列の確保&使い捨て(15-16 行目)を抑制しようとして添字をちまちま操作しても Ruby の場合は逆に遅くなったりする。実際にローカルでは遅くなった。


2023年07月23日 (日) [AtCoder] 精進。ABC142-F「Pure」(青 diff)。すんごく難しかった、というか、ややこしかった。これってうまい書き方があるんだろうか。「すべての頂点の入次数が 1、出次数が 1 であるような G の誘導部分グラフ」というのは要するに輪っかのことで、しかも輪っかを横切るショートカット辺が存在しないもののこと。N の2乗が通る制約ではあるけども、すべての頂点を始点にして輪っかを検出して、輪っかのパスを辿ってショートカット辺が存在しないことを確かめて許されるのかどうか。■提出 #43905266 (AC / 967 Byte / 77 ms)。再帰関数のパラメータとしてパスを表す配列と、再訪確認のためにパスをメンバとするハッシュ表と、調べたけどダメだったことをメモするハッシュ表を渡している。もっとうまいやりようがありそうなものだ。■輪っかの検出ロジックは、終点を決めて、終点の隣接頂点の1つを始点にして終点への到達可否を調べる。そのときに始点や中継点において、終点に到達したパスの中に2つ以上の隣接頂点(←始点や中継点にとっての)が含まれていないことを確認している。2つ以上の隣接頂点が関わる輪っかはショートカット辺がある輪っかなので。これを愚直に繰り返すと TLE になったので、ダメだった結果をメモしている。輪っかにならなかったケースとショートカット辺があったケース。メモの利用は中途半端で、輪っかの終点を切り替えるごとにリセットしてしまっている。輪っかなんだから(輪っかを検出するための便宜上の)終点をどこに取ったとしてもショートカット辺があるせいでダメだったという結果は共有できるはずなんだけどしていない。あと必要だったのは終点を含まないところで輪っかができてしまうケースの除外。それはいま関心がある部分ではないので後の処理を待ってほしい。再帰関数で同一頂点への再入を検知してリターンしないとスタックを使い尽くしてしまった。■これって強連結成分を定型的に列挙してショートカット辺をちょちょいと調べて終わりだったりしたのでは? 自分は何か輪っかの検出で苦労して、輪っかの検出とショートカット辺の確認を同時にやろうとしてさらに苦しくなっている雰囲気。DFS 2回の SCC 分解がそらで書けるほど身についてないから仕方ないんだけど。■日記を書くために自分の解法を書いていたらこれがなんで AC なのかわからなくなってきた。それでひとつの発見があった。最初に「要するに輪っかのことで、しかも輪っかを横切るショートカット辺が存在しないもののこと」と書いたけど、ショートカット辺はただのショートカット辺ではなくて長さ1のショートカット辺だった。誘導部分グラフに含まれる辺と含まれない辺を考えれば当然のことだけど。そうするとひとつの疑問がわいて、どんなループでも、許されない長さ1のショートカット辺を本式の、ループを構成する辺として採用することで答えにすることができるのかどうか。できそうな気がする。それで実装が簡単になるかと思って別解を書き始めたけど、べつにそんなことはなかった。■提出 #43935918 (AC / 1048 Byte / 69 ms)。長くはなったけどややこしさは減って、シンプルに閉路検出&コンパクト化で答えが得られるようになった。■この問題を選んだきっかけはこの前の ABC311-C「Find it!」で閉路検出を書いたからで(#43848448)、その目論見が外れてやけにややこしい解法に当初はなったけど、別解ができてみると狙いが外れていなかったことがわかる。グラフの形がちょっと複雑になって、閉路のコンパクト化という処理を追加したものがこの Pure という問題だった。


2023年07月21日 (金) [AtCoder] 精進。東京海上日動プログラミングコンテスト2023(AtCoder Beginner Contest 299)-G「Minimum Permutation」(黄 diff)。愚直解法が作れたら、あとはセグメント木を知っていますかというだけの問題になる。愚直解法とは。M 個の数について末尾の位置を求め、その最小値(もっとも前の位置)を I とする。I とはどういう数か。A 数列のうち I までの範囲からはどんな要素を選んで先頭に配置しても良い。なんなら I 番目を先頭にしても良い。そうしても1から M までの数が欠けることがないので。もちろん最小の要素を選んでこれを答えの先頭の要素とする。以降は選んだ値を取り除き A 数列の範囲を限定して繰り返し、2番目3番目……M 番目の要素を選んでいく。■提出 #43807822 (AC / 757 Byte / 1384 ms)。実装はごくごく素直で難しくない。Ruby によるすべての提出を見ると、セグメント木とヒープを併用するもの、セグメント木と二分探索を併用していて倍速いもの、Array#tally と while ループでゴルフをしているくっそ速いものなどいろいろ。自分のはセグメント木を2つ使っていて一番遅い部類。1つ使ったら2つ使うのも同じだと思ったけど、定数倍の面で最善の選択ではなかったみたい。セグメント木を使わない解法はさっぱりわかんない。セグメント木を使わないで N^2 にならない理由がわからない。■末尾の位置の最小値は最初にソートしておけば Array#shift で更新していける。で、その最前線に向かって増加列を作る。最前線を更新する。増加列を作る。繰り返し。みたいな?■提出 #43911137 (AC / 277 Byte / 278 ms)。そうみたい。Array#tally と、答えの配列(B)と、答えの配列のどこまでが確定しているか(i)と、値から答えの配列の添字を引く逆引きインデックス(I)を使っている。答えの配列の後半の未確定部分に増加列を記録している。最初は配列を2つ使って答えと増加列を保存していたけど、1つの配列でいいなと。そうすれば値の移し替えがいらないなと。最初は増加列の中にすでに同じ値が含まれているかどうかを調べるのに二分探索を使っていたけど、逆引きインデックスがあれば定数時間だなと。tally の使い道はある値が最後に出現する位置を知るためなので、ゴルフをしているのでなければ Array にくらべてオーバーヘッドの大きい Hash を使うことに合理性はない。すでに 213 ms の提出が存在している。■伝わらないことを承知で書くけども、「N^2 にならない理由がわからない」と書いていたときには、増加列を途中までコミットするという操作が欠けていた。どういうことか。各数字の末尾の位置の最前線に到達したときに、必要に迫られて増加列を答えの数列の一部としてコミットする。そのときにコミットした最後の要素の次の位置からまた増加列の作成を開始するなら、それは手戻りであり線形時間の処理では済まなくなる。増加列の前半だけを答えにコミットして後半を残しておくことで、増加列の作成を現在の位置からそのまま継続することができる。手戻りがない。最初はこの手戻りが解決できなくて解けなかった。次にセグメント木を思い出して解けた。しかし NlogN の時間がかかる。途中までコミットするという操作を発見してとうとう線形時間の処理になった。こういう工夫のしがいのある問題好き。たとえば「Simplified Reversi」(20200920p01.03) とか。


2023年07月17日 (月) 「あたま よわい」「顔 強い」 簡潔すぎる処方薬の説明にツッコミ殺到 思わずドキッとする表現に「声出して笑った」(1/2 ページ) - ねとらぼ」■これってやさしい日本語ってことなんだろうか。かえって難しいと思った。副詞を使うことも許されないの?


2023年07月16日 (日) [AtCoder] 精進。昨日あった freee プログラミングコンテスト2023(AtCoder Beginner Contest 310)-E「NAND repeatedly」(水 diff)。実は確かめるまでもなくサンプルの1に書いてあったのだけど、「⊼ は結合法則を満たさない」。だから右側からの累積和を使って左側から数え上げていくことはできない。できない? 昨日は左側からの累積和をどうにかしてうまく数えられないかと四苦八苦していたが、成果なし。一日経ってあらためて NAND について解釈してみた。右側から0を作用させるとき、左の値によらず1になる。右側から1を作用させるとき、左の値が反転する。右側から0を作用させたときに無条件に値が決まるから結合法則が成り立たなくなったりするんだろうか。ありがたくない性質。だけど f(i,j) を考えるとき、i と j のあいだに 0 があるなら i の値によらず f の値が決まるということでもある。i から j に向かって値を作用させていくとき、0の位置で f 値が1に固定されるわけなので、以降は共通。■提出 #43667957 (AC / 450 Byte / 528 ms)。コメントに書いたけど、0 の位置をまず調べて、その隣接する位置を i<j とするとき、「f(,i)=1 として f(,k) (i<=k<j) の和」「それの累積和」を考えた。できないと思ったけど結局右側からの累積和で解いている。現在の位置より右にある最初の0以降を範囲の右端とする f の値は累積和を引いてわかる。最初の0までにある1の連続を右端とする f の値は 01010... か 10101... のどちらかなのでこれもすぐにわかる。■昨日は考えずにテキトーに累積和というのか4つの値を記録する DP というのかをがちゃがちゃしていたのがまずかったな。いや違う。考えてはいた。袋小路だっただけで。Ruby によるすべての提出を見ると、自分のは長いし遅いし、かなり回りくどいことをしているみたい。えっっっ?■F 問題「Make 10 Again」は正解方針が引けなかったな。「確率とか期待値の問題って方針を決めるところに難しさがある。正解方針が引ければ答えが合うけど、答えにたどりつけない方針を引いてしまいがち。当てもんをやっている」。D 問題「Peaceful Teams」を踏まえて和が 10 になる組み合わせを全部列挙していいのかと思ったけど、包除がうまく扱えなかった。和が 10 になるまでの DP もやったけど合わない。たぶんだけど「~が存在する」確率を尋ねてはいけないのではないかと思う。解けないから。作問者は反省してほしい。■D 問題までも書いておこうね。自分のすべての提出。AC までに使った時間が A=1分半、B=7分半、C=3分、D=29分。以下ふりかえり。■A 問題「Order Something Else」。候補は2つ。定価もしくは余計なものを買って割引価格。■B 問題「Strictly Superior」。価格とスペックの比較。価格で負けていなくて機能で勝っているか、機能で負けていなくて価格で買っているか。100 ビットのビット演算でやったけど、機能の比較が間違いやすそうですごく緊張した。f1==f1&f2 とか f2==f2&f1 とか、いかにも取り違えそう。幸いペナルティは免れた。■C 問題「Reversible」。順逆を区別しない文字列の同一性判定。辞書順で先か後かどちらかを順逆の代表にすると決めて Hash に突っ込むだけ。■D 問題「Peaceful Teams」。これが時間内に解けた最後の問題。制約が 10 以下と小さいけど、さすがに T(=10) の N(=10) 乗は許されない。どうやってチームを作ろうか。最初は仕切りを使って T チームの人数配分を決めた後で N 人の順列を当てはめようとした。たとえば N=4人、T=3チームのとき、チームへの人数配分が 2-1-1 の場合を考えると、そこに配置する人の並びが 12-3-4、12-4-3、13-2-4、13-4-2 などになる。だけど重複がありますね。2人チームに人1と人2を割り当てる場合と人2と人1を割り当てる場合を区別してはいけない。まだある。2つある1人チームに人3と人4を割り当てる場合と人4と人3を割り当てる場合を区別してはいけない。同一人数のチームの並びと、同一チーム内での人の並びを区別しないようにサイズの階乗で割り算する必要がある。そうやって個々のケースに対応するなら場合の数を考える意味がない。相性問題への対処もわからない。もう手作業で個別具体的にチームを組んでいいんじゃないか。最終的に採用した方針がこう。T 個の空のチームを予め用意する。人1から順番に参加するチームを総当たりで試させる。そのときに相性を考慮する。N 人全員が行き先を決めたときに T チームすべてに人がいたならカウント1アップ。1つ1つ数えて間に合う制約なんです。相性チェックも総当たりで突き合わせて全然問題ない。処理の流れさえ決まればやることは愚直でいい。再帰関数を使った深さ優先探索で。これが T(=10) の N(=10) 乗でない理由は、部屋というのを構成メンバーで区別しているから。16 行目の「break if is.empty?」がポイント。空きチームが5個あるときに、5個ともに入ってみる必要はないよね。■■■E 問題。kotatsugame さんの動画を見ていました。「j を動かしながら今0のやつと1のやつの個数を求めておけばいいですね。」 あっはい(その通りですね)。(動画中断)。提出 #43692228 (AC / 144 Byte / 138 ms)。ちょー簡単だった! 脳みそがお留守だったと言うほかない。