競プロ解説 [ABC345D]
はじめに
本記事は、日々の精進で出会った問題の一部を丁寧目に解説することで、筆者と読者のアルゴリズム力を向上させることを目的としています。
問題
方針(全探索)
タイルを左上から順番に敷き詰めていくことを考えます。タイルを並べる順番の選び方は
2 次元配列で管理する
タイルを タイルを管理するデータ構造として一番初めに思いつくのは
for タイルを設置する順番 { // O(N!)
for タイルを設置する向き { // O(2^N)
'next_tile: for 順番と向きが指定されたタイルたち {
for (i, j) in まだタイルが置かれていないマス { // O(HW)
if タイルの左上が (i, j) になるように配置できる { // O(HW)
タイルを配置する //O(HW)
continue 'next_tile;
}
}
}
if 全てのマスにタイルが設置されている { //O(HW)
println!("Yes");
return;
}
}
}
println!("No");
タイルを設置する回数が高々
TLE
してしまいます。
以上の考察からわかるように、
タイルをビット列で管理する
各マスの状態はタイルの有無の u128
で十分です。
さらに、ビット演算を活用して以下の操作を
操作 | ビット演算 |
---|---|
タイルを |
tile <<= 1 |
タイルを設置できるか調べる | tile & grid == 0 |
タイルをマスに設置する | grid |= tile |
番兵法で折り返しをチェックする
タイルの移動にビットシフトを利用すると、マス目の右端でタイルが折り返してしまうことがあります。これをチェックするために、マス目の右端に
回答例
use itertools::Itertools;
use proconio::input;
fn main() {
input! { n: usize, h: u8, w: u8, ab: [(u8, u8); n], }
let (h, w) = (h.min(w), h.max(w));
// 同じ操作をクロージャでまとめる。
let convert_to_bit =
|a: u8, b: u8| (0..a).fold(0u128, |acc, i| acc + (((1 << b) - 1) << i * (w + 1)));
let filled = convert_to_bit(h, w);
let overflowed = u128::MAX - (1 << h * (w + 1) - 1) + 1;
let sentinel = u128::MAX - overflowed - filled;
let (tile_h, tile_v): (Vec<_>, Vec<_>) = ab
.into_iter()
.map(|(a, b)| (convert_to_bit(a, b), convert_to_bit(b, a)))
.unzip();
// 使用するタイルの組を指定して高速化
for i in 1..=n {
for set in (0..n).combinations(i) {
// タイルのサイズの合計がマス目のサイズと一致しない場合はダメ。「枝刈り」という。
if set.iter().fold(0, |acc, &i| acc + tile_h[i].count_ones()) as u8 != h * w {
continue;
}
for order in set.iter().copied().permutations(i) {
for rotated in 0..1 << i {
let mut grid = 0;
// 向きと順番が指定されたタイルを設置していく
'next_tile: for mut tile in (0..i).map(|i| match rotated & 1 << i {
0 => tile_h[order[i]],
_ => tile_v[order[i]],
}) {
tile <<= (sentinel & grid).trailing_ones();
while tile & overflowed == 0 {
if tile & (sentinel | grid) == 0 {
grid |= tile;
continue 'next_tile;
}
tile <<= 1;
}
// 設置できないタイルがあるとダメ。ここで、for rotated ...
// にジャンプすると、次の if 文が不要
break;
}
if grid == filled {
println!("Yes");
return;
}
}
}
}
}
println!("No")
}
-
たとえば、ビット列が
Vec<u64>
で実装されているとき です。A = 64 bitset_fixed
クレートを参照してください。 ↩︎
Discussion