Guards In The Storehouse | Codeforces 845F

题目链接

给定一个 \(n \times m\) 的网格,有些位置是障碍,其他是空地。

在一个空地放灯可以照亮这个灯向右,向下第一个障碍前的所有方格。

求有多少种在空地上放灯的方案,使得最多 \(1\) 个空地没有被照亮,对 \(10^9+7\) 取模。

\(nm \le 250\)

\(\land\) 是逻辑与,\(\lor\) 是逻辑或。

首先 \(nm \le 250 \Rightarrow \min\{n,m\} \le 15\)

如果 \(n < m\),就可以将行列转置,问题不变,但 \(m \le 15\) 了。

容易想到对每一行状压,状压一行中每个格子向上第一个障碍前是否有灯(即这个格子是否有向下的光)。

这样时间复杂度过高,感觉行不通,于是考虑压轮廓线,按照从上到下,从左到右的顺序放灯。

\(f_{i\,a,b,S}\) 表示目前将要决定格子 \(i=(x,y)\) 放不放灯,\(a\) 表示从 \(i\) 向左第一个障碍前是(\(1\))否(\(0\))有灯(即 \(i\) 左边是否有向右的光),\(b\) 表示有几个目前已决定有没有放灯的空地没有被照亮,而 \(S\) 是压的是第 \(x\) 行前 \(y-1\) 个格子和第 \(x-1\) 行后 \(m-y+1\) 个格子上是(\(1\))否(\(0\))有向下的光。

图中的情况 \(a = 0, b = 1, S = \{0,0,1,0,1\}\)

转移就三种情况(先不考虑从一行最后一个格子转移到下一行第一个格子的情况):

  • 格子 \(i\) 是障碍,那么它会挡住向右和向下的光,形式化地: \[ a \rightarrow 0,S \rightarrow S \setminus \{y\}\\\\ \]
    转移到 ```f[nxt][0][b][~(~S | 1 << y)]```。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    - 格子 $i$ 是空地,在格子 $i$ 放灯,那么它会产生向右和向下的光。
    $$
    a \rightarrow 1,S \rightarrow S \cup \{y\}
    $$
    ```f[i][a][b][S]``` 转移到 ```f[nxt][1][b][S | 1 << y]```

    - 格子 $i$ 是空地,不在格子 $i$ 放灯,那么还要考虑 $i$ 会不会被照亮。

    如果 $a=1 \lor y \in S$,那么 $i$ 会被照亮,$a,b$ 和 $S$ 都不变,否则 $i$ 不会被照亮, $b$ 必须为 0,转移后变为 $1$,$a$ 和 $S$ 不变。

    当 $i$ 是一行最后一个格子时,唯一区别是转移后 $a$ 变为 $0$,因此在代码里无需单独讨论。

    答案就是格子 $(n+1,1)$ 的所有 ```f``` 值之和,因为状态的定义是目前**将要**决定这个格子放不放灯。

    代码:

    ```cpp
    #include <bits/stdc++.h>
    #define rep(i, l, r) for(int i = (l); i <= (r); i++)
    #define per(i, r, l) for(int i = (r); i >= (l); i--)
    #define mem(a, b) memset(a, b, sizeof a)
    #define For(i, l, r) for(int i = (l), i##e = (r); i < i##e; i++)

    using namespace std;
    typedef long long ll;
    const ll P = 1e9 + 7;
    int n, m, f[255][2][2][1 << 15]; char s[255][255];

    int main() {
    cin >> n >> m;
    For(i, 0, n) scanf("%s", s[i]);
    if(n < m) {
    For(i, 0, n) For(j, i + 1, m) swap(s[i][j], s[j][i]);
    swap(n, m);
    }
    f[0][0][0][0] = 1;
    For(i, 0, n) For(j, 0, m) rep(a, 0, 1) rep(b, 0, 1) For(S, 0, 1 << m) {
    int x = f[i * m + j][a][b][S], nxt = i * m + j + 1;
    if(!x) continue;
    if(s[i][j] == 'x') (f[nxt][0][b][~(~S | 1 << j)] += x) %= P;
    else {
    (f[nxt][j < m - 1][b][S | 1 << j] += x) %= P;
    if(a | (S >> j & 1)) (f[nxt][a & (j < m - 1)][b][S] += x) %= P;
    else if(!b) (f[nxt][0][1][S] += x) %= P;
    }
    }
    int as = 0;
    rep(b, 0, 1) For(S, 0, 1 << m) (as += f[n * m][0][b][S]) %= P;
    cout << as;
    return 0;
    }