bitsetの使い方
概要
bit演算子を勉強しようしよう思っていたのですが、以下の問題にぶち当たってようやくやる気になりました。bitsetの使い方がわかればbit演算子の威力がご理解いただけると思う。自分は思い知った。。。
問題
https://atcoder.jp/contests/abc147/tasks/abc147_e
bitsetの有用性
この問題は回答方針が決まってもTLE
となってしまう。最初はデータの保存をsetで管理していたのが問題だった。AC
の人のコードを見てみると、bit演算を使って何やらかっこよく記述していた。尊敬の眼差しでした。自分なりの理解をした結果、bitsetでできることは、同じ値を足す、引くといった作業を繰り返す処理を一発でできるということ。ん、どういうこと?
bitset
その前に、bitsetについて簡単に解説する。bitsetは右からi番目(0からスタート)にその数値があるかないかを表す。百間は一見にしかずということで以下の例を見て欲しい。
数値 | bitset |
---|---|
0 | 0 |
1 | 10 |
2 | 100 |
3 | 1000 |
[1, 3] | 1010 |
[0, 2, 3] | 1101 |
同じ値を足す、引く
このbitsetを使えば、同じ値を足す、引くという操作が一瞬で行える。例えば、以下のようなリストの各値に2を足したいとする。
a = [1, 2, 1, 4]
pythonで普通に書くと以下のようになる。リストの要素が大きくなるとかなり時間がかかってしまう。
for i in range(4): a[i] += 2
bitsetを使うと、この処理を一瞬でできる。
b = b << 2 #2を足す b = b >> 2 #2を引く
コード
def run(): ofs = 1000 H, W = map(int, input().split()) dp = [[0 for w in range(W)] for h in range(H)] a = [list(map(int, input().split())) for i in range(H)] b = [list(map(int, input().split())) for i in range(H)] c = [[abs(a[i][j] - b[i][j]) for j in range(W)] for i in range(H)] dp[0][0] = (1 << ofs + c[0][0]) | (1 << ofs - c[0][0]) for h in range(1, H): dp[h][0] = (dp[h-1][0] << c[h][0]) | (dp[h-1][0] >> c[h][0]) for w in range(1, W): dp[0][w] = (dp[0][w-1] << c[0][w]) | (dp[0][w-1] >> c[0][w]) for h in range(1, H): for w in range(1, W): v = c[h][w] dp[h][w] = (dp[h-1][w] << v) | (dp[h-1][w] >> v) dp[h][w] |= (dp[h][w-1] << v) | (dp[h][w-1] >> v) s = list(bin(dp[-1][-1] >> ofs)) for i in range(len(s)): if s[-1-i] == '1': print(i) break if __name__ == '__main__': run()
参考文献
Python ビット演算 超入門
ABC147 E Balanced Pathでbitset高速化を完全に理解する
Balanced Path [AtCoder Beginner Contest 147 E]