Wiki » History » Version 3
hori, 01/03/2023 08:12 PM
| 1 | 1 | hori | # 公開の記録 |
|---|---|---|---|
| 2 | 2 | hori | |
| 3 | {{toc}} |
||
| 4 | |||
| 5 | 3 | hori | ## ABC 274 E の TLE <2023-01-03 Tue 19:49> |
| 6 | |||
| 7 | 独力で解けなかったので https://atcoder.jp/contests/abc274/submissions/35636696 の回答を参考に実装してみたら、TLE になってしまったので、原因を調べてみた。 |
||
| 8 | |||
| 9 | ほぼ同じ実装である https://atcoder.jp/contests/abc274/submissions/35887265 が AC なのに、なぜ TLE したのかと思って細かく見ていったところ、DP の漸化式に相当する「訪問済みノードのビットセット」の更新処理に問題があることが分かった。 |
||
| 10 | |||
| 11 | 公式回答の Python では以下のように、ビットセット `s` に `(1<<j)` というビットを追加するために XOR を使用している。 |
||
| 12 | |||
| 13 | ``` |
||
| 14 | if dp[j][s^(1<<j)]>new_dist: dp[j][s^(1<<j)]=new_dist |
||
| 15 | ``` |
||
| 16 | |||
| 17 | Ruby に書き換える際に、この XOR を深く考えず安直にコピーしてしまったために、余計な遅延が乗って時間制限をオーバーしてしまったということだった。 |
||
| 18 | |||
| 19 | 実際簡単なベンチマークでビット XOR 演算子とビット OR 演算子の速度の違いを比較してみる。 |
||
| 20 | |||
| 21 | ``` |
||
| 22 | $ cat xor_or.rb |
||
| 23 | S = (1<<17)*17*17 |
||
| 24 | |||
| 25 | t1 = Time.now |
||
| 26 | S.times do |i| |
||
| 27 | x = i | (S - 1) |
||
| 28 | end |
||
| 29 | t2 = Time.now |
||
| 30 | p t2 - t1 |
||
| 31 | |||
| 32 | t1 = Time.now |
||
| 33 | S.times do |i| |
||
| 34 | x = i ^ (S - 1) |
||
| 35 | end |
||
| 36 | t2 = Time.now |
||
| 37 | p t2 - t1 |
||
| 38 | ``` |
||
| 39 | |||
| 40 | ABC274E の最大規模に相当するループ回数で実行してみると、以下のように 1 秒程度の差が生じた。 |
||
| 41 | XOR は OR よりも遅いことを認識しておかないといけない。 |
||
| 42 | |||
| 43 | ``` |
||
| 44 | $ ruby xor_or.rb |
||
| 45 | 2.797993445 |
||
| 46 | 3.898028078 |
||
| 47 | ``` |
||
| 48 | |||
| 49 | 少し関係ないが、本問題を検証している上で、ビットカウントのルーチンの効率について調べてみたので、一応書いておく。 |
||
| 50 | |||
| 51 | ``` |
||
| 52 | $ cat popcount_test.rb |
||
| 53 | # https://stackoverflow.com/questions/1639723/ruby-count-the-number-of-1s-in-a-binary-number に基づく最適なルーチン |
||
| 54 | def popcount(x) |
||
| 55 | m1 = 0x55555555 |
||
| 56 | m2 = 0x33333333 |
||
| 57 | m4 = 0x0f0f0f0f |
||
| 58 | x -= (x >> 1) & m1 |
||
| 59 | x = (x & m2) + ((x >> 2) & m2) |
||
| 60 | x = (x + (x >> 4)) & m4 |
||
| 61 | x += x >> 8 |
||
| 62 | return (x + (x >> 16)) & 0x3f |
||
| 63 | end |
||
| 64 | |||
| 65 | M = 20 |
||
| 66 | |||
| 67 | t1 = Time.now |
||
| 68 | (1 << M).times do |i| |
||
| 69 | popcount i |
||
| 70 | end |
||
| 71 | t2 = Time.now |
||
| 72 | |||
| 73 | # 整数型に [] でアクセスすると bit がセットされているかどうかで 0, 1 を返す |
||
| 74 | t3 = Time.now |
||
| 75 | (1 << M).times do |i| |
||
| 76 | M.times.count {|j| i[j] > 0} |
||
| 77 | end |
||
| 78 | t4 = Time.now |
||
| 79 | |||
| 80 | t5 = Time.now |
||
| 81 | (1 << M).times do |i| |
||
| 82 | i.digits(2).size |
||
| 83 | end |
||
| 84 | t6 = Time.now |
||
| 85 | p t2 - t1 |
||
| 86 | p t4 - t3 |
||
| 87 | p t6 - t5 |
||
| 88 | |||
| 89 | $ ruby popcount_test.rb |
||
| 90 | 0.355551277 |
||
| 91 | 3.333421961 |
||
| 92 | 0.839962182 |
||
| 93 | ``` |
||
| 94 | |||
| 95 | ということで、独自ルーチン `popcount` を用いるのが最速のようだ。 |
||
| 96 | ライブラリに登録しておこう。 |
||
| 97 | |||
| 98 | 2 | hori | ## AtCoder ABC 276 E について <2022-11-06 Sun 00:07> |
| 99 | |||
| 100 | https://atcoder.jp/contests/abc276/editorial/5162 |
||
| 101 | 公式解法のとおり、BFS や DSU を用いたにも関わらず TLE を解決できなかったので振り返る。 |
||
| 102 | アルゴリズムの問題ではなく、Ruby の基本的なデータ構造の問題で遅くなっていた。 |
||
| 103 | |||
| 104 | グリッド問題の入力は以下のような文字列で与えられるが、これをどういうデータ構造に落とすのがよいかという問題。 |
||
| 105 | |||
| 106 | ``` |
||
| 107 | 5 7 |
||
| 108 | .#...#. |
||
| 109 | ..#.#.. |
||
| 110 | ...S... |
||
| 111 | ..#.#.. |
||
| 112 | .#...#. |
||
| 113 | ``` |
||
| 114 | |||
| 115 | 安直なものでは以下のような格納方式が考えられる。 |
||
| 116 | |||
| 117 | 1. 1 つの文字列として格納する方式 |
||
| 118 | ``` |
||
| 119 | ".#...#...#.#.....S.....#.#...#...#." |
||
| 120 | ``` |
||
| 121 | |||
| 122 | 2. 1 次元の文字の配列として格納する方式 |
||
| 123 | ``` |
||
| 124 | [".", "#", ".", ".", ".", "#", ".", ".", ".", ...] |
||
| 125 | ``` |
||
| 126 | |||
| 127 | 3. 各行ごとの文字列の配列として格納する方式 |
||
| 128 | ``` |
||
| 129 | [".#...#.", |
||
| 130 | "..#.#..", |
||
| 131 | "...S...", |
||
| 132 | "..#.#..", |
||
| 133 | ".#...#."] |
||
| 134 | ``` |
||
| 135 | |||
| 136 | 4. 文字の 2 次元配列として格納する方式 |
||
| 137 | ``` |
||
| 138 | [[".", "#", ".", ".", ".", "#", "."], |
||
| 139 | [".", ".", "#", ".", "#", ".", "."], |
||
| 140 | [".", ".", ".", "S", ".", ".", "."], |
||
| 141 | ...] |
||
| 142 | ``` |
||
| 143 | |||
| 144 | 私はこれまで割と雰囲気で 1. や 2. を選んでしまっていた。 |
||
| 145 | また、過去に別問題を変に学習してしまったようで 2D 配列は 1D 配列にした方が速い (添字を毎回変換していた) と思い込んでいたのもある。 |
||
| 146 | |||
| 147 | 一回自前で計測しておくと安心ということで測ってみた。 |
||
| 148 | 1000x1000 の適当なグリッドを作って各セルを走る処理を 5 回実行した時間を比較する (単位は秒)。 |
||
| 149 | |||
| 150 | | | | データロード | 全処理時間 | |
||
| 151 | |---|--------------------|--------------|------------| |
||
| 152 | | 1 | 文字列 | 0.22 | 4.35 | |
||
| 153 | | 2 | 一次元文字配列 | 4.96 | 6.49 | |
||
| 154 | | 3 | 行ごとの文字列配列 | 0.006 | 5.12 | |
||
| 155 | | 4 | 二次元文字配列 | 0.21 | 2.84 | |
||
| 156 | |||
| 157 | データロードにおいて一次元文字配列が遅いのは `array1 + array2` のような連結処理をしているからで、これは以前遅いことを学習済み。 |
||
| 158 | 文字列の添字でのアクセスよりも `string.chars` で文字れ配列に分割した方が、データロードには多少時間がかかるが、アクセスは高速になるようだ。 |
||
| 159 | |||
| 160 | ということで、今後はグリッド問題が出た時は二次元文字配列で処理するようにしてみる。 |