O(3^N)で部分集合の列挙をする実装

例えば最小クリーク被覆問題のように、部分集合から別の部分集合に遷移し、かつ、遷移が集合が大きくなる方向に限定されるようなとき、  O(3^N)アルゴリズムで解くことができます。

最小クリーク被覆問題

atcoder.jp

この問題を解くとき、 A \cup B \leftarrow (A, B) みたいな遷移をしたいので、集合  A \cup B A を全て走査します。これらはそれぞれ  O(2^N) なので、全体で  O(4^N) かかるように思えます。しかし、 A A \cup B の部分集合なので、 A \cup B を固定したとき、 A の候補として、全ての集合を走査する必要はないはずです。

ビット列を使って表すと、 A \cup B が 1101 である時、A の候補は 1101, 1100, 1001, 0101, 1000, 0100, 0001, 0000 であり、1010 などは  A \cup B の部分集合ではないため候補たり得ません。よって  A \cup B の部分集合  A の候補を列挙する実装は、 A \cup B の立っているビットのみに注目して走査することになります。

あるビット列からその部分集合全てを列挙するのは面倒な気がしますが、実は非常に簡潔で高速な書き方があります。

let mut a = set;
while a > 0 {
    // set の部分集合 a を使った何らかの操作
    a = (a - 1) & set;
}

このように書くと、集合 set の部分集合 a を全て列挙できます。

(a - 1) & set

の部分が重要で、これは set の部分集合 a よりも小さい set の部分集合のうち最大のものを表します。かなり非自明な感じがしますが、落ち着いて考えてみるとたしかにそのとおりです。

計算量は  2^N + _NC_1 2^(N-1)+_NC_2 2^(N-2) + ... + 1 = \sum ^{N} _{ k = 0 } {_NC_k}2^k = (1+2)^N = 3^N より  O(3^N) となります。

例題の解答

fn main() {
    let (r, w) = (std::io::stdin(), std::io::stdout());
    let mut sc = IO::new(r.lock(), w.lock());
    let n: usize = sc.read();
    let m: usize = sc.read();
    let mut graph = vec![0; n];
    for _ in 0..m {
        let a = sc.usize0();
        let b = sc.usize0();
        graph[a] |= 1 << b;
        graph[b] |= 1 << a;
    }

    let mut dp = vec![n; 1 << n];
    dp[0] = 1;
    for i in 0..n {
        for mask in 0..(1 << n) {
            if dp[mask] == 1 && (graph[i] & mask) == mask {
                dp[mask | (1 << i)] = 1;
            }
        }
    }

    for set in 1..(1 << n) {
        let mut a = set;
        while a > 0 {
            assert_eq!(set & a, a);
            dp[set] = dp[set].min(dp[set ^ a] + dp[a]);

            a = (a - 1) & set;
        }
    }
    println!("{}", dp[(1 << n) - 1]);
}

pub struct IO<R, W: std::io::Write>(R, std::io::BufWriter<W>);

impl<R: std::io::Read, W: std::io::Write> IO<R, W> {
    pub fn new(r: R, w: W) -> Self {
        Self(r, std::io::BufWriter::new(w))
    }
    pub fn write<S: ToString>(&mut self, s: S) {
        use std::io::Write;
        self.1.write_all(s.to_string().as_bytes()).unwrap();
    }
    pub fn read<T: std::str::FromStr>(&mut self) -> T {
        use std::io::Read;
        let buf = self
            .0
            .by_ref()
            .bytes()
            .map(|b| b.unwrap())
            .skip_while(|&b| b == b' ' || b == b'\n' || b == b'\r' || b == b'\t')
            .take_while(|&b| b != b' ' && b != b'\n' && b != b'\r' && b != b'\t')
            .collect::<Vec<_>>();
        unsafe { std::str::from_utf8_unchecked(&buf) }
            .parse()
            .ok()
            .expect("Parse error.")
    }
    pub fn usize0(&mut self) -> usize {
        self.read::<usize>() - 1
    }
    pub fn vec<T: std::str::FromStr>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.read()).collect()
    }
    pub fn chars(&mut self) -> Vec<char> {
        self.read::<String>().chars().collect()
    }
}