Project

General

Profile

Wiki » History » Version 6

hori, 04/25/2023 10:17 PM

1 1 hori
# 公開の記録
2 2 hori
3 6 hori
{{>toc}}
4 2 hori
5 5 hori
## <2023-04-25 Tue 22:05> ABC297 E
6
7
https://atcoder.jp/contests/abc297/tasks/abc297_e
8
9
優先度付きキューを用いた解法は比較的容易に思いつくことはできた。
10
ただ、ちょっとした違いが微妙な速度の差につながり TLE するかどうかに効いてしまい、解決まで時間がかかってしまったので、メモしておく。
11
AC した解法は以下である。
12
13
```
14
N, K = gets.split.map(&:to_i)
15
A = gets.split.map {|i| i.to_i}.uniq
16
n = A.size
17
pq = PriorityQueue.new {|x, y| x < y}
18
pq.push(0)
19
tmp = {}
20
x = -1
21
while x < K
22
  val = pq.pop
23
  x += 1
24
  n.times do |i|
25
    if tmp[val + A[i]].nil?
26
      pq.push(val + A[i])
27
      tmp[val + A[i]] = true
28
    end
29
  end
30
end
31
puts val
32
```
33
34
これだとサンプルの 3 は 2.7 秒程度で通る。
35
一方、当初 while ループの中を以下のようにしてしまっていて、5.8 秒くらいかかり、TLE になっていた。
36
37
```
38
while x < K
39
  val = pq.pop
40
  next if tmp[val]
41
  tmp[val] = true
42
  x += 1
43
  n.times do |i|
44
    pq.push(val + A[i])
45
  end
46
end
47
```
48
49
優先度付きキューにとりあえず入れて、出してから既に出現済みかどうかをチェックするよりも、優先度付きキューに入れる前に、出現済みかどうかをチェックして除外する方が、キューに入れるという重めの操作をを減らすことができて速い、ということのようだ。
50
ハッシュのチェックは、優先度付きキューの取り出し処理よりも速い、これはまあ実感として分かるが、そこが差になるということは知っておかねばなるまい。
51
52 4 hori
## <2023-04-24 Mon 23:31> Anthy の日本語入力
53
54
手元の作業マシンは Ubuntu 20.04 のデスクトップ版を利用していて、日本語入力には Mozc を使っていた。
55
ところが、どうも誤変換が多くて使い物にならない [^1] ということで Mozc は諦め、Anthy に戻した。
56
いつ頃からそうなったのか不明だが、キーバインドを柔軟に割り当てられるようになったため、問題なく戻せた。
57
58
個人的に一番重要と思うのは IME のオン・オフ (日本語入力と直接入力) のために指を大きく移動させたくないというのと、オンとオフを異なるキーバインドにしたいという点だ。
59
いくつか試してみたが今一番上手く言っている気がするのは `Ctrl + j` でひらがなモード、`Ctrl + ;` で半角英数モード、とすることだ。
60
これが指の位置的には最小の動きで済むというのと、意外に他のキーバインドと干渉しない点が気に入っている。
61
62
[^1]: 変換の精度も問題だが、変換候補の文節の切り方が異常で、どう考えてそうは切らないだろうというところで切られた単位で変換候補が表示されるのが辛いところ。
63
64
## <2023-01-03 Tue 19:49> ABC 274 E の TLE
65 3 hori
66
独力で解けなかったので https://atcoder.jp/contests/abc274/submissions/35636696 の回答を参考に実装してみたら、TLE になってしまったので、原因を調べてみた。
67
68
ほぼ同じ実装である https://atcoder.jp/contests/abc274/submissions/35887265 が AC なのに、なぜ TLE したのかと思って細かく見ていったところ、DP の漸化式に相当する「訪問済みノードのビットセット」の更新処理に問題があることが分かった。
69
70
公式回答の Python では以下のように、ビットセット `s` に `(1<<j)` というビットを追加するために XOR を使用している。
71
72
```
73
     if dp[j][s^(1<<j)]>new_dist: dp[j][s^(1<<j)]=new_dist
74
```
75
76
Ruby に書き換える際に、この XOR を深く考えず安直にコピーしてしまったために、余計な遅延が乗って時間制限をオーバーしてしまったということだった。
77
78
実際簡単なベンチマークでビット XOR 演算子とビット OR 演算子の速度の違いを比較してみる。
79
80
```
81
$ cat xor_or.rb
82
S = (1<<17)*17*17
83
84
t1 = Time.now
85
S.times do |i|
86
  x = i | (S - 1)
87
end
88
t2 = Time.now
89
p t2 - t1
90
91
t1 = Time.now
92
S.times do |i|
93
  x = i ^ (S - 1)
94
end
95
t2 = Time.now
96
p t2 - t1
97
```
98
99
ABC274E の最大規模に相当するループ回数で実行してみると、以下のように 1 秒程度の差が生じた。
100
XOR は OR よりも遅いことを認識しておかないといけない。
101
102
```
103
$ ruby xor_or.rb
104
2.797993445
105
3.898028078
106
```
107
108
少し関係ないが、本問題を検証している上で、ビットカウントのルーチンの効率について調べてみたので、一応書いておく。
109
110
```
111
$ cat popcount_test.rb
112
# https://stackoverflow.com/questions/1639723/ruby-count-the-number-of-1s-in-a-binary-number に基づく最適なルーチン
113
def popcount(x)
114
  m1 = 0x55555555
115
  m2 = 0x33333333
116
  m4 = 0x0f0f0f0f
117
  x -= (x >> 1) & m1
118
  x = (x & m2) + ((x >> 2) & m2)
119
  x = (x + (x >> 4)) & m4
120
  x += x >> 8
121
  return (x + (x >> 16)) & 0x3f
122
end
123
124
M = 20
125
126
t1 = Time.now
127
(1 << M).times do |i|
128
  popcount i
129
end
130
t2 = Time.now
131
132
# 整数型に [] でアクセスすると bit がセットされているかどうかで 0, 1 を返す
133
t3 = Time.now
134
(1 << M).times do |i|
135
  M.times.count {|j| i[j] > 0}
136
end
137
t4 = Time.now
138
139
t5 = Time.now
140
(1 << M).times do |i|
141
  i.digits(2).size
142
end
143
t6 = Time.now
144
p t2 - t1
145
p t4 - t3
146
p t6 - t5
147
148
$ ruby popcount_test.rb
149
0.355551277
150
3.333421961
151
0.839962182
152
```
153
154
ということで、独自ルーチン `popcount` を用いるのが最速のようだ。
155
ライブラリに登録しておこう。
156
157 2 hori
## AtCoder ABC 276 E について <2022-11-06 Sun 00:07>
158
159
https://atcoder.jp/contests/abc276/editorial/5162
160
公式解法のとおり、BFS や DSU を用いたにも関わらず TLE を解決できなかったので振り返る。
161
アルゴリズムの問題ではなく、Ruby の基本的なデータ構造の問題で遅くなっていた。
162
163
グリッド問題の入力は以下のような文字列で与えられるが、これをどういうデータ構造に落とすのがよいかという問題。
164
165
```
166
5 7
167
.#...#.
168
..#.#..
169
...S...
170
..#.#..
171
.#...#.
172
```
173
174
安直なものでは以下のような格納方式が考えられる。
175
176
1. 1 つの文字列として格納する方式
177
   ```
178
   ".#...#...#.#.....S.....#.#...#...#."
179
   ```
180
181
2. 1 次元の文字の配列として格納する方式
182
   ```
183
   [".", "#", ".", ".", ".", "#", ".", ".", ".", ...]
184
   ```
185
186
3. 各行ごとの文字列の配列として格納する方式
187
   ```
188
   [".#...#.",
189
    "..#.#..",
190
    "...S...",
191
    "..#.#..",
192
    ".#...#."]
193
   ```
194
195
4. 文字の 2 次元配列として格納する方式
196
   ```
197
   [[".", "#", ".", ".", ".", "#", "."],
198
    [".", ".", "#", ".", "#", ".", "."],
199
    [".", ".", ".", "S", ".", ".", "."],
200
    ...]
201
   ```
202
203
私はこれまで割と雰囲気で 1. や 2. を選んでしまっていた。
204
また、過去に別問題を変に学習してしまったようで 2D 配列は 1D 配列にした方が速い (添字を毎回変換していた) と思い込んでいたのもある。
205
206
一回自前で計測しておくと安心ということで測ってみた。
207
1000x1000 の適当なグリッドを作って各セルを走る処理を 5 回実行した時間を比較する (単位は秒)。
208
209
|   |                    | データロード | 全処理時間 |
210
|---|--------------------|--------------|------------|
211
| 1 | 文字列             | 0.22         | 4.35       |
212
| 2 | 一次元文字配列     | 4.96         | 6.49       |
213
| 3 | 行ごとの文字列配列 | 0.006        | 5.12       |
214
| 4 | 二次元文字配列     | 0.21         | 2.84       |
215
216
データロードにおいて一次元文字配列が遅いのは `array1 + array2` のような連結処理をしているからで、これは以前遅いことを学習済み。
217
文字列の添字でのアクセスよりも `string.chars` で文字れ配列に分割した方が、データロードには多少時間がかかるが、アクセスは高速になるようだ。
218
219
ということで、今後はグリッド問題が出た時は二次元文字配列で処理するようにしてみる。
Go to top