[BZOJ4572][Scoi2016]围棋

[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;
}
原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/7002827.html