P3290 [SCOI2016]围棋

题目描述
近日,谷歌研发的围棋 AI——AlphaGo 以 4:14:1 的比分战胜了曾经的世界冠军李世石,这是人工智能领域的又一里程碑。

与传统的搜索式 AI 不同,AlphaGo 使用了最近十分流行的卷积神经网络模型。在卷积神经网络模型中,棋盘上每一块特定大小的区域都被当做一个窗口。例如棋盘的大小为 5\times 65×6,窗口大小为 2\times 42×4,那么棋盘中共有 1212 个窗口。此外,模型中预先设定了一些模板,模板的大小与窗口的大小是一样的。

下图展现了一个 5\times 65×6 的棋盘和两个 2\times 42×4 的模板:

在这里插入图片描述

对于一个模板,只要棋盘中有某个窗口与其完全匹配,我们称这个模板是被激活的,否则称这个模板没有被激活。

例如图中第一个模板就是被激活的,而第二个模板就是没有被激活的。我们要研究的问题是:对于给定的模板,有多少个棋盘可以激活它。

为了简化问题,我们抛开所有围棋的基本规则,只考虑一个 n\times mn×m 的棋盘,每个位置只能是黑子、白子或无子三种情况,换句话说,这样的棋盘共有 3^{n\times m}3
n×m
种。此外,我们会给出 qq 个 2\times c2×c 的模板。

我们希望知道,对于每个模板,有多少种棋盘可以激活它。强调:模板一定是两行的。

输入格式
输入数据的第一行包含四个正整数 n,m,cn,m,c 和 qq,分别表示棋盘的行数、列数、模板的列数和模板的数量。

随后 2\times q2×q 行,每连续两行描述一个模板。其中,每行包含 cc 个字符,字符一定是 W,B 或 X 中的一个,表示白子、黑子或无子三种情况的一种。

输出格式
输出应包含 qq 行,每行一个整数,表示符合要求的棋盘数量。由于答案可能很大,你只需要输出答案对 10^9+710
9
+7 取模后的结果即可。

输入输出样例
输入 #1复制
3 1 1 2
B
W
B
B
输出 #1复制
6
5
说明/提示
对于所有测试点:1\leq n\leq 1001≤n≤100,1\leq m\leq 121≤m≤12,1\leq c\leq 61≤c≤6,1\leq q\leq 51≤q≤5。

测试点编号 约定
11 n=3n=3,m=4m=4,c=2c=2
22 n=4n=4,m=4m=4,c=3c=3
33 n=2n=2,m=9m=9,c=6c=6
44 n=2n=2,m=12m=12,c=3c=3
55 n=2n=2,m=12m=12,c=5c=5
66 n=10n=10,m=8m=8,c=3c=3
77 n=10n=10,m=10m=10,c=5c=5
88 n=100n=100,m=10m=10,c=5c=5
99 n=100n=100,m=12m=12,c=5c=5
1010 n=100n=100,m=12m=12,c=6c=6

做这道题目时马上就想到了轮廓线DP,“至少有一次匹配”可以转化为“禁止出现匹配”。

设dp[i][j][mask][k][l]考虑到了i行j列这个位置,而mask是一个状压,
表示当前轮廓线有那些位置可以和第一行完全匹配。
但是1…c−1这些列显然不可能完全匹配,
所以maskmask只需要记录m-c+1m−c+1个二进制位。
kk表示第ii行1\dots j1…j位最多能匹配到模板串第一行的哪个位置,
ll表示第ii行1\dots j1…j位最多能匹配到模板串第二行的哪个位置。
显然,当l=cl=c且maskmask的第一个二进制位(也就是代表了i,ji,j正上方的位置)为11时,
就会出现一次和模板串的完整匹配,因此这种转移是不合法的。
否则,其他情况下我们都可以转移。
新的kk, ll可以用KMP求出。
新的maskmask相比原来的maskmask要去掉第一位,然后新加入一位:若k=ck=c则新加入的位为11,否则为00.
DP数组的前两维可以滚动使用,这样优化了空间
在dp完后要把结果累加到dp[i][j][mask][0][0]dp[i][j][mask][0][0]上:因为下一行又要重新开始匹配了。
时间复杂度
***O(nm⋅2 m−c+1 ⋅c 2)。***

代码

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

const int MAXN=100,MAXM=12;
const char biao[3]={'W','B','X'};
int n,m,c,q,nxt[2][MAXM+5],dp[2][1<<MAXM][7][7];
char s[2][MAXM+5];

int main() {
	cin>>n>>m>>c>>q;
	int totways=pow_mod(3,n*m);
	while(q--){
		//memset(nxt,0,sizeof(nxt));
		for(int t=0;t<=1;++t){
			cin>>(s[t]+1);
			nxt[t][1]=0;
			for(int i=2,j=0;i<=c;++i){
				while(j&&s[t][j+1]!=s[t][i])j=nxt[t][j];
				if(s[t][j+1]==s[t][i])++j;
				nxt[t][i]=j;
			}
		}//kmp
		memset(dp,0,sizeof(dp));
		dp[0][0][0][0]=1;
		int len=m-c+1;
		int M=1<<len;
		int cur=0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				cur^=1;
				memset(dp[cur],0,sizeof(dp[cur]));
				for(int mask=0;mask<M;++mask){
					for(int k=0;k<=c;++k){
						for(int l=0;l<=c;++l)if(dp[cur^1][mask][k][l]){
							for(int x=0;x<=2;++x){
								int nk=k;
								while(nk&&(nk==c||s[0][nk+1]!=biao[x]))nk=nxt[0][nk];
								if(s[0][nk+1]==biao[x])nk++;
								
								int nl=l;
								while(nl&&(nl==c||s[1][nl+1]!=biao[x]))nl=nxt[1][nl];
								if(s[1][nl+1]==biao[x])nl++;
								
								if(j>=c&&nl==c&&(mask&1))continue;
								
								int newmask=mask;
								if(j>=c){
									newmask>>=1;
									newmask|=(nk==c)<<(len-1);
								}
								
								add(dp[cur][newmask][nk][nl],dp[cur^1][mask][k][l]);
							}
						}
					}
				}
			}
			for(int mask=0;mask<M;++mask){
				for(int j=0;j<=c;++j){
					for(int k=0;k<=c;++k)if(j||k){
						add(dp[cur][mask][0][0],dp[cur][mask][j][k]);
						dp[cur][mask][j][k]=0;
					}
				}
			}
		}
		int ans=0;
		for(int mask=0;mask<M;++mask){
			add(ans,dp[cur][mask][0][0]);
		}
		ans=mod2(totways-ans);
		cout<<ans<<endl;
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值