10101
で表せる。全部で5人なら、これは 1-3-5 位しか考えられないが、全部で 10 人なら 2-4-6 位もありうる。しかし全部で 10 人いて他に 101011111
というピースがあるなら、やはり 1-3-5 位しか考えられない(※ MSB を1位としました)。こうなってくるととりあえず全部を試してみるしか答えを出す方法はないよね、という気持ちになる。1階層ごとに特定の1ピースを埋める DFS をやるんだけど、DFS の呼び出し経路には関心がない。どういうことか。1階層目と2階層目が受け持つピースがどちらも 1
と 1
という形だったとする。3階層目のピースにとって関心があるのは、そして結果に影響するのは、どのビットが空いているかということだけなので、同じ2ビットのどちらとどちらを1ピース目と2ピース目が埋めているかという情報には関心がない。むしろ積極的にその区別を捨てて結果の使い回しをしたい。メモ化です。というわけで、前半ではピースの形を確定する UnionFind を、後半ではピースを N ビットに隙間なく埋めるメモ化再帰をする2段構えの問題だった。要素技術は F 問題までで身につけているはずで、組み合わさったことで F 問題だったのかなと。G 問題ではないなと。ならこれも実装問題だったのか。突破できませんでした。腕力が足りない。■他所で見かけたのでここでも書くんだけど、置きにくいピース(※)から置くような小細工を WA だった最初の提出から行っている。これをやらなくても TLE にはならないだろうけど、とにかく答えを見つければいいという DFS の場合、優先順位を付けて早期に判定ができると劇的に実行時間が縮まることがあって気持ちがいいので、とりあえずやってみて沼にはまるのがいいと思う。その先にヒューリスティック沼があるかは知らない。この問題では、再帰を深く潜ってからやっぱりだめでしたーと判定されていたかもしれないものが、比較的浅い階層で無理だと判断できるようになる効果があったと思う。※置きにくいピースを短絡的にビット長の長いものとしたけど、ポップカウントもいい指標になりそうだと気がついた。1000001
と 1111
のどちらが置きにくいかという話。自分が明確に誤っていたのは、ビット長が同じでポップカウントが異なる 110001
と 101111
の比較で、単純に数値比較で大きい方(最初の方)を置きにくいと判断したところ。小細工だしフレイバーなので許される手抜きだとは思うけど。0o
なので。意外すぎる観測結果なんだけど、ABC000 が罠になるってことある?■B 問題「Dentist Aoki」。やります。T[i] = 1-T[i]
で更新したけど、T[i] ^= 1
の方が参照の繰り返しがなくて良かった。T.sum を答えにしたかったから数値配列にしたけど、真偽値配列にしたなら T.count が使える。そして、いつも期待を裏切られるのだけど、無引数の T.count は T のサイズを返す。自分の期待は false と nil を除いたものの数なんだけど、そうではない。そのことを irb で確かめるまでは今日もまた、もしかしてと期待していた。■C 問題「Sort」。N 個の順列は N-1 回のスワップでソートできるので、やるだけではある。でも添字と値が入り交じるのでややこしいんだ。コメントで頭の中を整理していた痕跡がある>提出 #52573395。ちゃんと効果があって、コメントに教えられて提出前にバグが見つけられた。■D 問題「New Friends」。知っている問題ですね。前回解いたときは UnionFind をしながら辺の数も管理するような解答を書いたのだけど、辺の数の総数が M として与えられているので、別に数える必要はないのである。それを覚えていたので今日は M が使えた。■E 問題「Toward 0」。最近解けていなかった期待値の問題だけど、これだけ素直な問題設定だとループのある試行でも式が立てられる。メモ化再帰で。5分かかっていないから9分かけた C 問題より簡単だったと言えるのでは。■F 問題「Transpose」。AC できただけでも嬉しいことだけど、さすがに1時間は時間をかけすぎている。何ができなかったかって、対応する括弧に囲まれた文字列を再帰的に取り出すことに苦労していた。私は再帰関数が書けません。やっと書けても TLE だった>提出 #52607490。それはまあ、文字列の配列を連結することを繰り返す代わりに、直接文字列を出力することで通ったのだけど(提出 #52610168)、Ruby での他の提出と比較すると遅いので出来の悪い解答であるらしい。たくさんのオブジェクトを new しているのが明らかに悪いが、class を使わないと頭の中の整理がつかないので仕方がない。コンテキストを分けて小さな部分問題を解くことに専念するためにクラスがある。■C 問題をやるだけと書いたけど、最初からそう捉えられたわけではない。ABC と AGC の区別がついていなかった頃に書いた日記に過程が書いてある>20190907p01。x
をくっつけたら2番目のルールは無視できる。あとは T を元にして S をスキャンする。■D 問題「Divide Interval」。問題文が難しいよね。文というか式が。何度も読んで理解したところでは、ある2の冪乗 W があって、その2冪 W でアラインされた幅 W を持つ範囲が良い数列だと言っている。この説明でわかりやすくなったかは疑問。2つの2冪 w と W があって、w<W のとき、幅 W の良い数列の中と隣に、幅 w の良い数列はきっちり隙間なく整列するので、とりあえず最大の W を L...R の範囲内に見つけて、その左右に W 未満で範囲に収まる最大の w を再帰的に求めていけばいいように思う。考察半分実装半分でどちらもやや難しくやや大変だから、普段より高めの 450 点だったかと思う。22 分かけている。ビット演算で何かをやろうとしてあきらめて 60 通りの全探索に切り替えるまでに時間を使った。■E 問題「Weighted Tic-Tac-Toe」。メモ化再帰でとりあえずやってみたら通りました。盤面は3進数で。Takahashi と Aoki を区別するために手番を知りたくなって、どうやって知るか困ったけど、残りの白マスの偶奇で判別できた。メモ化関数の戻り値の仕様次第では二人の名前を区別する必要がないと思うのだけど、そう期待して実装を始めたのだけど、勝者の名前を返すような仕様にしてしまったので困ってしまっていた。終了条件が2つあって、一方の条件ではスコアが無関係だからそういう仕様に誘導されてしまった。24 分かけている。かけた時間から判断すると、D と E がどちらも 450 点だったのはまこと適切だったと思う。■F 問題「Subsequence LCM」。解けてないよ。愚直解法で TLE×14/AC×23。A の中の同一要素をまとめて処理すると TLE×12/AC×25。2つだけ AC が増えた。LCM でフィルタしていた部分を GCD で判定するようにして不用意に大きすぎる値を生み出さないようにも注意したけど、たぶんそれによる改善はあんまりない。これ以上のアイデアはない。■■■D 問題。最初の提出 #52331543 はせっかく定義した IJ 関数が一度しか呼び出されていなくてもったいないので、それを LR 関数として再定義してスクリプトの後半でも利用するようにした。提出 #52388999。16 行くらいあった後半部分が3行になった。while 文が2つある構成は同じだけど、ループの本体が関数を呼び出すだけの1行になった。各所で読んだのだけど、セグメント木についてはまったく頭に浮かびませんでした。セグメント木の図を思い浮かべれば問題の理解が早かったと思う。でもそれが思い浮かんだ時点でもう問題を理解してるよね。oox
の繰り返しを必要な長さ出力するだけだった。だけど各 i についてそのつど判定する方が簡単だったのでそのように。■B 問題「Farthest Point」。距離の比較をするのにルートはいらない。Ruby には Math.hypot (sqrt をとる) の2乗の値を返すメソッドがあると思ってリファレンスを見たけど見つからなかった。たぶん Complex#abs2 のことが頭にあったのだと思う。解法は愚直総当たりで最初に見つかったものを答えにする。素直に書けば「番号が小さいもの」という条件は自然に満たされる。■C 問題「Colorful Beans」。色でグループ化してグループ内の最小値の最大値を出力する。要は悲観主義者が最もましな選択肢を選んだ場合がどうなるかという話。この C 問題まではプログラミング言語の扱いが問われている。やりたいことが書けますかと。Ruby で参加しているなら、C 問題は Array#group_by を知っていますか、という問題だった。■D 問題「Medicines on Grid」。グラフですよね。S と T と薬のマスを頂点として、同じ頂点を2度通らずに S から T へ到達できますかという問題。これは訪問済み頂点を記録して DFS でやろうか。そして頂点間の繋がりがグリッドで与えられていて、探索をしなければ明らかにならない2段構えになっている。最初はきれいに2段に分けて解こうとしたんだけど、面倒くさくなった。最初のグリッド探索のついでに到達可否の判断をしてもいいじゃないか。プライオリティキューを使わずにテキトーにキューに探索地点を追加してエネルギーを記録していった。これに 50 分くらいかけたんですよ。それはダメ。■E 問題「Minimize Sum of Distances」。全方位木 DP。頭が破壊されました。こういうのは終了したあとでじっくり落ち着いて書きたい。終了3分後>提出 #52114566 (RE)。頂点番号を1始まりのままにしていたのに、0 から N-1 を処理対象にしてしまったせいでエラーになっている。終了 13 分後>提出 #52115938 (AC)。結局惜しくはなかった。今日になって全体が見通せる状態でイチから書いたもの>提出 #52179331 (AC)。最初からこれがすらすら書けないのは理解が遅いってことだよ。..
(終端を含む) に代えて ...
(終端を含まない) を使うこと。要するに、長さ1の部分文字列を表現する方法が確保されているかどうかが WA と AC を分けている。一般的な文字列検索と正規表現検索との違いで顕在化しがちな問題として、特定の位置の空文字列を表現する方法があるかどうかというものがある。普通のテキスト検索では長さ0の文字列を探すことも見つけることもないので、表現能力の不足に気がつけないことがある。C++ のイテレータが開区間ではなく半開区間なのは、こういう理由からもよくできてるなと思う。いいものはどんどんまねしよう。そしてこれはトリビアだけど、サイズ N の配列 A があるとき、A の末尾の要素は A[N-1] であり A[N] の読み取りは不正だけど、A+N が A+N-1 より大きいことが C 言語の規格で保証されているらしい。でないと範囲が表現できないもんね。■C 問題「Ideal Holidays」。配点が +50 の 350 点ということでやや難しめだと予想できる。実際その通り。予定が A+B 周期で何日目に当たるかを求めてソートして、順番に各要素が休日の1日目にあたると仮定して N 個後ろの予定が A 日目までに収まっているかを判定する。提出直後に同じ日に複数の予定がある場合に正しく判定できないことに気が付いたのだけど、今見ると制約によって重複が排除されていた。優しい。そして今一度よく考えると、D%(A+B) したときに重複が生まれる可能性がある。重複する予定が3つあったとして、最初に処理する予定に限って正しく判定ができるので、結果的に問題がなかったのだ。■D 問題「Popcount and XOR」。C は最大で 60 個のビットを持っており、立っているビットを X または Y のどちらかに割り振る。割り振るのはどちらでも良いが、popcount(C)<a+b のときは C のビットが立っていない位置で X と Y の両方にビットを割り振って打ち消し合わせる必要があるので、a と b の大きい方に 1 のビットを割り振るのがよい。最初に WA×1/RE×1 を出したが(#51837959)。7分後に AC (#51842648)。両方の提出でやっていることは同じ。ただ、RE/WA を出した方では、不可能なケースを最初に判定するようにしていた。一方の AC 提出の方では、操作をしたあとで、結果が満足しているかどうかで出力を切り替えた。要するに、不可能なケースの判定に漏れがあったのだ。おそらく C の0のビットが a, b の大きさに対して不足している場合を弾き損ねていた。最初に提出する前に不安はあった。だから普段書かない assert、……はないから raise を埋め込んで前提が間違っていないかを確かめていた(その結果 RE が出ている)。残念。■E 問題「Set Add Query」。所要時間によれば CD より簡単でしたよね。まず、各時点での集合のサイズというのが操作をシミュレートすることで予め求めておける。次に、各 x (1≦x≦N) について集合に含まれていた区間を調べる。これはクエリを順番になぞりながら値 x からクエリ番号の列を逆引きできるように記録をとればよい。クエリの列から2つずつ取り出したものが x が集合に含まれていた区間。■今回は水 diff と青 diff が問題セットに含まれていなかった。今日の自分は緑 diff 以下の簡単な問題に滅法強いマンとしてレートを稼いでいる。仮にここに水 diff の問題がプラスされると、水 diff の問題にはそこそこ時間がかかるので、水 diff 以下の問題に滅法強い人にタイム差をつけられて相対的にパフォーマンスが下がる。さらに青 diff の問題がプラスされると、自分は青 diff の問題は時間内にほとんど解けないので、青 diff が解ける人に点差をつけられて、やはり相対的に自分のパフォーマンスが下がる。しかし水も青もなかったから、自分を含めて黄 diff が解けないグループがひとまとめにされて、緑 diff の問題で優劣が判定された。それで青パフォをもらうのは、なんだかなーだよね。最終更新: 2024-04-03T08:25+0900
AtCoder をプラットフォームとして利用する有志コンであり、AtCoder の問題ではないけども、競プロカテゴリとして AtCoder に分類しています。
リアルタイム参加はしていない。ABCDFHIE をこの順に解いたのでふりかえって書く。
出現数を数える。Array#tally そのもの。
2本の線が一致することはないので、必ず3つか4つに分割される。ソートするなりしてうまく分類する。
うまくできませんでした。
数列が a,1,b であるとする。そうすると、b=a+1、a=b+1、b=a+1 と操作を繰り返すことで無限に大きな要素を作ることができる。A[1]=1 の場合であっても、A[1] を無限大にすることができる。初期数列が1以上の要素を含んでいることが必要十分条件だと思いました。証明は知りません。
間違えました。最初は、2つの要素を足して1以上になることが必要十分条件だと思ったんだよね。証明は知らないとか言ってっからだよ。
すごーく難しかった。4回間違えた。順番に WA×13、WA×3、WA×2、WA×1。
まずは問題を理解する。X 座標と Y 座標について、正負どちら方向に移動することもできるけど、移動量の絶対値が一致していなければいけない。
どういう操作を構成するか。最初からずっと想定していたのは、まずは X 座標もしくは Y 座標の、ずれが小さい方の数字を一致させる。その後はずれが大きい方を、偶数回の操作で一致させる。偶数回というのは、先に合わせた方の座標をずらしたくないので、移動量を等分して打ち消し合わせるということ。
この考え方で WA×1 まで行ったのだけど、一方のずれに対して他方のずれが非常に大きい場合に答えが微妙に合わなくなる。
結論を書くと、最初に目指すべきポイントは近い方の座標ではなかった。偶数回の操作の折返し点を目指すべきだった。要するに、X 座標のずれが DX、Y 座標のずれが DY だとして、DX<DY なら最初に目指すべき点は DX+(DY-DX)/2 だったということ。DX だけ移動してから DY-DX を偶数回で移動するのではなく、DX+(DY-DX)/2 移動してから (DY-DX)/2 移動する操作を構成するのだった。
ARC で 400 点ぐらい配点してもいい問題だと思います。400 点というのは、数学的センスがある人は瞬殺するけども、自分は1時間以上苦しむという、そういう問題。
任意の部分列について成り立たなければいけないので、一番極端なケースを想定する。つまり、N=1 のケースと N=2 のケース、そして中央値に対して極端に平均を下げるような列を。
N=1 のケースでは中央値と平均値が一致する。N=2 のケースでは a1<a2 なる列の中央値が a1 なので平均値が必ず中央値を上回る。N>=3 の場合で N が偶数のとき、中央値より大きい値の数が小さい値の数より1つ多くなるので、平均値は大きくなりがち。だから N が奇数の場合だけを考える。もっといえば、N=3 で成り立つなら N=5 でも N=7 でも成り立つと思うんだけど、そんな気がするだけ。
N=3 のケース。a1<a2<a3 としても構わないのでそうする。平均を下げたいので a1 は最低の 0。a3 がいくつなら平均が a2 以上になるだろうか。2×a2 が最低ライン。N の制約が 20 万以下ということだけど、要素が倍々に増えていくなら ai≦10^9 の制約から実質的な上限は 30 程度になる。
問題文の表現にひっかかりがありますね。「氷の張ってある道をなるべく通りたくないです
」「氷の張ってある道を通る時間を最小化して街 N に移動するとき
」。要するに氷が張っている道を通ることもあると言っている。01BFS みたいに、氷が張っている道を0回通る場合の最小値、1回通る場合の最小値、2回の場合の……、を N にたどり着くまで繰り返して求めれば良さそう。うまくやればいらないかもだけどプライオリティキューを使ってダイクストラ法をした。
01BFS ということでこれは2本のキューを切り替えながら使った。
氷の上を何回通ったかと経過時間とを1つの値にエンコードすることで、キューを1本だけ使う普通のダイクストラ法になった。
まずは問題を理解する。ある要素を1つ後ろに移動し、その際に K を加算する、というのが操作。
最初は後ろにある要素に操作を繰り返して大きくすることで昇順の列を作ることを考えた。最低2つの要素があれば、交互に操作対象に選ぶことで任意の回数 K を加算することができる。2つの要素の初期の差を解消したり拡大したりすることはできないけど、1回の操作で昇順にできるので問題ない。
これの問題は K=1 で Ai が上限の 10^9 に近いとき。初期数列が A1=10^9、A2=1、A3=1 だったとして、A2 と A3 に操作を繰り返して A1 以上にすることは可能だけど、操作回数が 50 万を超えてしまう。
次に考えたのは、最小値を列の前に持ってくること。i 番目に最小値があるとして、i-1 から 1 まで下りながら操作することで A1 から A_{i-1} にそれぞれ K を加算しつつ、Ai を先頭に持ってくることができる。以降これより後ろの数列に対してどんな操作を繰り返したとしても、Ai より小さい値が後ろに出現することはなく昇順が保たれる。
初期数列が降順にソートされている場合が最悪ケースだけど、N*(N-1)/2 回くらいの操作で昇順になる。N≦1000 だからちょうど操作上限の 50 万回を下回るくらい。
しょうもないミスをした。
vector の任意の位置から値を追い出すときに、末尾の要素とスワップしてからポップするというテクニックがある。順番に意味がないときは使って損がない。
順番に意味はあったのだ。順番が保存されていないと正しい操作対象が選べない。いや、自分は壊れた順番の中で正しい対象を選んでいたのだけど、そのせいで勘違いしたのだけど、ジャッジが正しさを検証できなければ意味がない。
300 点だけど難しかった。これが解けたのが嬉しくて今日の日記を書いているところがある。
とある赤い亀さんの日記を読みました。
学びのある問題だった。