[BZOJ4572][Scoi2016]围棋
试题描述
近日,谷歌研发的围棋AI—AlphaGo以4:1的比分战胜了曾经的世界冠军李世石,这是人工智能领域的又一里程碑。与传统的搜索式AI不同,AlphaGo使用了最近十分流行的卷积神经网络模型。在卷积神经网络模型中,棋盘上每一块特定大小的区域都被当做一个窗口。例如棋盘的大小为5×6,窗口大小为2×4,那么棋盘中共有12个窗口。此外,模型中预先设定了一些模板,模板的大小与窗口的大小是一样的。下图展现了一个5×6的棋盘和两个2×4的模板。对于一个模板,只要棋盘中有某个窗口与其完全匹配,我们称这个模板是被激活的,否则称这个模板没有被激活。例如图中第一个模板就是被激活的,而第二个模板就是没有被激活的。我们要研究的问题是:对于给定的模板,有多少个棋盘可以激活它。为了简化问题,我们抛开所有围棋的基本规则,只考虑一个n×m的棋盘,每个位置只能是黑子、白子或无子三种情况,换句话说,这样的棋盘共有3n×m种。此外,我们会给出q个2×c的模板。我们希望知道,对于每个模板,有多少种棋盘可以激活它。强调:模板一定是两行的。
输入
输入数据的第一行包含四个正整数n,m,c和q,分别表示棋盘的行数、列数、模板的列数和模板的数量。随后2×q行,每连续两行描述一个模板。其中,每行包含c个字符,字符一定是‘W’,‘B’或‘X’中的一个,表示白子、黑子或无子三种情况的一种。N<=100,M<=12,C<=6,Q<=5
输出
输出应包含q行,每行一个整数,表示符合要求的棋盘数量。由于答案可能很大,你只需要输出答案对1,000,000,007取模后的结果即可。
输入示例
3 1 1 2 B W B B
输出示例
6 5
数据规模及约定
见“输入”
题解
看到模板只有两行,就可以考虑用轮廓线 dp 做了。轮廓线的状态就一定分两行,对于上一行我们只需要关心是否完全匹配了模板的第一行就好了。
于是设计出状态 f(i, j, S, p1, p2) 表示现在在表格第 i 行第 j 列的位置,轮廓线状态为 S(如果某一位上二进制是 1 则表示这一位往前数 C 位得到的串与模板第一行完全相同),p1 和 p2 分别表示当前行已经填的串和模板的第一行、第二行的 KMP 上的匹配位置。转移显然。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <algorithm> using namespace std; int read() { int x = 0, f = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); } return x * f; } #define maxn 110 #define maxm 15 #define maxc 10 #define maxs 4096 #define MOD 1000000007 #define LL long long int n, m, C, f[2][maxm][maxs][maxc][maxc]; struct KMP { int S[maxc], f[maxc], to[maxc][3]; void init() { memset(f, 0, sizeof(f)); return ; } void getstr() { char c = getchar(); while(!isalpha(c)) c = getchar(); for(int i = 1; i <= C; i++, c = getchar()) if(c == 'X') S[i] = 0; else if(c == 'B') S[i] = 1; else S[i] = 2; for(int i = C + 1; i < maxc; i++) S[i] = 233; return ; } void getfail() { f[1] = f[2] = 1; for(int i = 2; i <= C; i++) { int j = f[i]; while(j > 1 && S[j] != S[i]) j = f[j]; f[i+1] = S[j] == S[i] ? j + 1 : 1; } for(int i = 1; i <= C + 1; i++) for(int c = 0; c < 3; c++) { int j = i; while(j > 1 && S[j] != c) j = f[j]; to[i][c] = S[j] == c ? j + 1 : 1; } return ; } } l1, l2; void up(int& a, int b) { a += b; if(a >= MOD) a -= MOD; return ; } int main() { n = read(); m = read(); C = read(); int q = read(); while(q--) { l1.init(); l1.getstr(); l1.getfail(); l2.init(); l2.getstr(); l2.getfail(); memset(f, 0, sizeof(f)); int all = (1 << m) - 1; bool cur = 0; f[cur][1][0][1][1] = 1; for(int i = 1; i <= n; i++, cur ^= 1) { memset(f[cur^1], 0, sizeof(f[cur^1])); int tmp = 0; for(int S = 0; S <= all; S++) up(tmp, f[cur][1][S][1][1]); for(int j = 1; j <= m; j++) for(int S = 0; S <= all; S++) for(int p1 = 1; p1 <= C + 1; p1++) for(int p2 = 1; p2 <= C + 1; p2++) { int& ans = f[cur][j][S][p1][p2]; if(!ans) continue; for(int c = 0; c < 3; c++) { int t1 = l1.to[p1][c], t2 = l2.to[p2][c]; if((S & 1) && t2 == C + 1) continue; int tS = S >> 1 | ((t1 == C + 1) << m - 1); if(j < m) up(f[cur][j+1][tS][t1][t2], ans); else up(f[cur^1][1][tS][1][1], ans); } } } int ans = 0; LL sum = 1; for(int S = 0; S <= all; S++) up(ans, f[cur][1][S][1][1]); for(int i = 1; i <= n * m; i++) (sum *= 3) %= MOD; printf("%d ", (int)((sum - ans + MOD) % MOD)); } return 0; }