制作地图

发布时间 2024-01-12 18:38:25作者: onlyblues

制作地图

贝茜正在学习制作扫雷地图。

作为初学者,它打算先从简单的一维地图做起,它准备制作一张 $1 \times n$ 大小的地图(即 $1 \times n$ 的方格矩阵)。

地图中含有地雷的方格,应标记为 *;不含地雷的方格,应标记为一个 $0$ 到 $2$ 之间的数字,用来表示其相邻方格中的地雷数量。

例如,001*2* 是一个合法的地图,因为每个数字的相邻 * 的数量与数字本身均保持一致;2* 是一个不合法的地图,因为数字 2 的相邻 * 的数量只有 $1$。

给定贝茜正在制作的地图,地图中的若干($0$ 个或更多)方格已被标记,其余($0$ 个或更多)方格尚未标记。

请你计算,如果将所有尚未标记的方格全部标记完毕,一共可能得到多少种不同的合法地图。

注意,已被贝茜标记的部分不一定保证合法,所以很有可能根本得不到任何合法地图。

输入格式

共一行,包含一个长度为 $n$ 的字符串,其中第 $i$ 个字符用来表示第 $i$ 个方格的状态。

字符串中只可能包含字符 *012?,其中 *012 的含义如题面描述,? 表示方格尚未标记。

输出格式

一个整数,表示可以得到的不同的合法地图的数量。

由于结果可能很大,请你输出对 $10^9+7$ 取模后的结果

数据范围

前 $4$ 个测试点满足 $1 \le n \le 10$。
所有测试点满足 $1 \le n \le 10^6$。

输入样例1:

?01???

输出样例1:

4

输入样例2:

?

输出样例2:

2

输入样例3:

**12

输出样例3:

0

输入样例4:

1

输出样例4:

0

 

解题思路

  用数字 $0$,$1$,$2$ 来分别字符 012。用数字 $3$ 来表示 *

  考虑 dp,当确定了第 $i$ 个位置的字符后,此时就可以判断第 $i-1$ 个位置上的字符是否合法(左右两边的字符都已经确定)。定义状态 $f(i,j,k)$ 表示由前 $i$ 个字符构成,且第 $1 \sim i-1$ 个位置上的字符都合法,第 $i-1$ 个位置上的字符是 $j$, 第 $i$ 个位置上的字符是 $k$ 的所有合法方案的数量。此时第 $i-1$ 和 $i$ 个位置上的字符已经确定,根据第 $i-2$ 个字符 $u$ 是什么来进行状态转移。当 $j=3$ 即第 $i-1$ 个位置上的字符是 *,那么不管左右两边是什么字符第 $i-1$ 个位置上的 * 都合法,因此 $f(i,j,k)$ 可以通过 $f(i-1,u,j), \, \left(u \in [0,3] \right)$ 转移得到。如果 $j \in [0,2]$ 即第 $i-1$ 个位置上的字符是数字,那么只有左右两边 * 的数量恰好等于 $j$,第 $i-1$ 个位置上的字符才合法,此时 $f(i,j,k)$ 可以通过 $f(i-1,u,j), \, \left(u \in [0,3] \; \text{and} \; j = [u \, \mathrm{=}\mathrm{=} \, 3] + [k \, \mathrm{=}\mathrm{=} \, 3] \right)$ 转移得到。

  另外为了方便字符串的下标从 $1$ 开始,由于第 $0$ 个位置和第 $n+1$ 个位置上没有雷,这里默认都用 0 来表示(只要不是 3 即可)。同时状态的第一维 $i$ 枚举到 $n+1$,最后统计答案的时候只用统计 $\sum\limits_{j=0}^{3}{f(n+1,j,0)}$ 即可。

  AC 代码如下,时间复杂度为 $O(n)$:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10, mod = 1e9 + 7;

char s[N];
int f[N][4][4];
string mp = "012*";

int main() {
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    s[n + 1] = '0';
    for (int i = 0; i <= 3; i++) {
        if (s[1] == '?' || s[1] == mp[i]) f[1][0][i] = 1;
    }
    for (int i = 2; i <= n + 1; i++) {
        for (int j = 0; j <= 3; j++) {
            if (s[i - 1] == '?' || s[i - 1] == mp[j]) {
                for (int k = 0; k <= 3; k++) {
                    if (s[i] == '?' || s[i] == mp[k]) {
                        for (int u = 0; u <= 3; u++) {
                            if (j == 3) f[i][j][k] = (f[i][j][k] + f[i - 1][u][j]) % mod;
                            else if (j == (u == 3) + (k == 3)) f[i][j][k] = (f[i][j][k] + f[i - 1][u][j]) % mod;
                        }
                    }
                }
            }
        }
    }
    int ret = 0;
    for (int i = 0; i <= 3; i++) {
        ret = (ret + f[n + 1][i][0]) % mod;
    }
    printf("%d", ret);
    
    return 0;
}

 

参考资料

  AcWing 5365. 制作地图(AcWing杯 - 周赛):https://www.acwing.com/video/5275/