Problem:
JOI 旗 (JOI Flag)

問題

情報オリンピック日本委員会では，今年の日本情報オリンピック (JOI) を宣伝するために， JOI のロゴをモチーフにした旗を作ることになった．旗は「良い旗」でなければならない．「良い旗」とは，アルファベットの J, O, I のいずれかの文字を，縦 M 行，横 N 列の長方形状に並べたもので， J, O, I が以下の図のように (すなわち，J の右隣に O が，その J の下隣に I が) 並んでいる箇所が旗の少なくとも 1 か所にあるものである．

以下の図に，「良い旗」の例を 2 つ示す．

以下の図に，「良い旗」ではない例を 2 つ示す．

いま M, N の値，および旗の一部の場所について J, O, I のどの文字にするかが決まっており，入力としてその情報が与えられる．考えられる「良い旗」は何通りあるかを計算し，その数を 100000 (=105) で割った余りを出力するプログラムを作成せよ．

入力

入力は 1 + M 行からなる．

1 行目には旗の大きさを表す 2 つの整数 M, N (2 ≤ M ≤ 20, 2 ≤ N ≤ 20) が空白で区切られて書かれている．

1 + i 行目 (1 ≤ i ≤ M) には，N 文字からなる文字列が書かれている．各文字は J, O, I, ? のいずれかであり， j 文字目 (1 ≤ j ≤ N) が J, O, I のいずれかである場合は i 行 j 列の場所の文字がそれぞれ J, O, I に決まっていること， ? である場合はまだ決まっていないことを表す．

出力

考えられる「良い旗」の個数を 100000 (=105) で割った余りを 1 行で出力せよ．

入出力例

入力例 1

2 3
??O
IIJ

出力例 1

4

入力例 1 においては，以下の図の 4 通りの「良い旗」が考えられる．

入力例 2

2 2
??
??

出力例 2

3

入力例 3

3 3
??I
???
O?J

出力例 3

53

入力例 4

5 4
JOI?
????
????
????
?JOI

出力例 4

28218

入力例 4 においては，「良い旗」は 2428218 通り考えられるので，それを 100000 (=105) で割った余りである 28218 を出力する．

問題文と自動審判に使われるデータは、情報オリンピック日本委員会が作成し公開している問題文と採点用テストデータです。

Solution in C++14 (judge verdict: Accepted)

#include <bits/stdc++.h>

using namespace std;

#define fst(t) std::get<0>(t)
#define snd(t) std::get<1>(t)
#define thd(t) std::get<2>(t)
#define unless(p) if(!(p))
#define until(p) while(!(p))

using ll = long long;
using P = std::tuple<int,int>;

const int dx[8] = {-1, 1, 0, 0, -1, -1, 1, 1}, dy[8] = {0, 0, -1, 1, -1, 1, -1, 1};

template <typename T>
T expt(T a, T n, T mod = std::numeric_limits<T>::max()){
    T res = 1;
    while(n){
        if(n & 1){res = res * a % mod;}
        a = a * a % mod;
        n >>= 1;
    }
    return res;
}

constexpr ll MOD = 100000;
int M, N;
std::string flag[30];
std::vector<int> v;
int dp[20][20][18000][2];

// pr: (r, c) 以前 N マスにあった"JO"を記録したもの
// flag_j: (r, c) の左隣に"J"があるか
// 条件を満たさない旗の個数を計算する
ll rec(int r, int c, int pr, int flag_j){
    if(r == M){
        return 1;
    }

    int idx = std::lower_bound(v.begin(), v.end(), pr) - v.begin();

    assert(v[idx] == pr);

    if(dp[r][c][idx][flag_j] != -1){
        return dp[r][c][idx][flag_j];
    }

    int nr = r, nc = c + 1;
    if(c + 1 == N){
        nr = r + 1;
        nc = 0;
    }

    ll res = 0;
    if(flag[r][c] == 'J' || flag[r][c] == '?'){
        res = rec(nr, nc, (pr << 1) & ((1 << N) - 1), nr == r);
    }

    if(flag[r][c] == 'O' || flag[r][c] == '?'){
        res = (res + rec(nr, nc, ((pr + flag_j) << 1) & ((1 << N) - 1), 0)) % MOD;
    }

    if(flag[r][c] == 'I' || flag[r][c] == '?'){
        unless(pr >> (N - 1) & 1){
            res = (res + rec(nr, nc, (pr << 1) & ((1 << N) - 1), 0)) % MOD;
        }
    }

    return dp[r][c][idx][flag_j] = res;
}

int main(){
    std::cin.tie(nullptr);
    std::ios::sync_with_stdio(false);

    std::cin >> M >> N;

    for(int i=0;i<(1<<N);++i){
        unless(i & (i >> 1)){
            v.emplace_back(i);
        }
    }

    int numQuestions = 0;
    for(int i=0;i<M;++i){
        std::cin >> flag[i];

        numQuestions += std::count(flag[i].begin(), flag[i].end(), '?');
    }

    memset(dp, -1, sizeof(dp));
    ll res = (expt<ll>(3, numQuestions, MOD) + MOD - rec(0, 0, 0, 0)) % MOD;
    std::cout << res << std::endl;
}