Project

General

Profile

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
ということで、今後はグリッド問題が出た時は二次元文字配列で処理するようにしてみる。
Go to top