天下一プログラマーコンテスト2013 決勝: A - 天下一有無

https://beta.atcoder.jp/contests/tenka1-2013-final/tasks/tenka1_2013_final_a

問題概要

N×Mのグリッドの各マスに対して「着色されたマスが縦・横・斜めで隣り合わない」かつ「最低1マスは着色されている」という条件のもと色を塗るとき、何通りの塗り方があり得るか求めよ。

N, M <= 15

解法

上の行から順に各マスを塗る・塗らないのbitDPをしていく。遷移を単純にやると215×215かかってしまうが、そもそも215の中には正しくない塗り方(塗りが隣り合う)がかなりの数含まれているため、そういうのを飛ばす枝刈りを入れると十分間に合う(M=15のとき正しい塗り方は1597通りしかない)。

感想

Aなのにむずいしどこにも解説ないし辛かった

指数時間アルゴリズムは枝刈りが本質になり得るという学びがあった(これが想定解なのかわからんが)

コード (D言語)

import std.stdio, std.array, std.string, std.conv, std.algorithm;
import std.typecons, std.range, std.random, std.math, std.container;
import std.numeric, std.bigint, core.bitop, std.bitmanip;

immutable long MOD = 10^^9 + 7;

bool valid(int mask) {
    return !((mask & (mask << 1)) || (mask & (mask >> 1)));
}

bool valid2(int mask1, int mask2) {
    return !((mask1 & (mask2 << 1)) || (mask1 & mask2) || (mask1 & (mask2 >> 1)));
}

void main() {
    auto s = readln.split.map!(to!int);
    auto H = s[0];
    auto W = s[1];
    auto masks = iota(1<<W).filter!(a => valid(a)).array;

    auto dp = new long[][](H+1, 1<<W);
    dp[0][0] = 1;

    foreach (r; 0..H) {
        foreach (mask1; masks) {
            if (dp[r][mask1] == 0) continue;
            foreach (mask2; masks) {
                if (!valid2(mask1, mask2)) continue;
                dp[r+1][mask2] += dp[r][mask1];
                dp[r+1][mask2] %= MOD;
            }
        }
    }

    auto ans = (dp[H].sum - 1) % MOD;
    ans.writeln;
}