P3290 [SCOI2016]围棋

本文解析了谷歌AlphaGo如何利用卷积神经网络在围棋比赛中战胜世界冠军,通过模板匹配分析激活棋盘的方法,以及高效的动态规划算法解决激活棋盘数量问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述
近日,谷歌研发的围棋 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;
}

### 关于SCOI2005 互不侵犯问题的DFS解法 对于SCOI2005 互不侵犯这一问题,采用深度优先搜索(DFS)的方法同样能够解决问题。这种方法通过尝试每一种可能的情况来寻找满足条件的结果。 #### DFS解题思路 在解决此问题时,DFS算法会逐行放置国王,并确保任何两个国王之间不会互相攻击。具体来说: - 使用二进制数表示每一行的状态,其中`1`代表当前位置已放置国王,`0`则为空白。 - 对于每一个新的行,在所有未被先前行中的国王威胁的位置上尝试放置新国王。 - 如果当前行的所有列都遍历完毕,则回溯至上一行继续探索其他可能性。 - 当成功放置了指定数量的国王后,计数器加一;如果某次递归达到了最后一行且仍未完成目标,则返回并调整之前的决策。 为了提高效率,还需要提前计算出哪些状态是合法的——即不存在连续两位都是`1`的状态,这可以通过简单的枚举实现[^4]。 #### Python代码实现 下面是一个基于上述逻辑编写的Python程序片段用于求解该问题: ```python def dfs(row, col_mask, left_diag, right_diag): global n, k, ans if row == n: if sum(bin(col)[2:].count('1') for col in cols) == k: ans += 1 return for i in range(1 << n): if bin(i).count('1') + sum(cols[:row]) > k: continue ok = ((~col_mask & ~left_diag & ~right_diag & (i)) == i) if not ok or '11' in bin(i): continue new_col_mask = col_mask | i new_left_diag = (left_diag | i) << 1 new_right_diag = (right_diag | i) >> 1 dfs(row + 1, new_col_mask, new_left_diag, new_right_diag) n, k = map(int, input().split()) cols = [0]*n ans = 0 dfs(0, 0, 0, 0) print(ans) ``` 这段代码定义了一个名为`dfs()`函数来进行深度优先搜索,它接收四个参数分别表示当前处理的是哪一行(`row`)、当前列上的占用情况(`col_mask`)、左斜线方向上的占用情况(`left_diag`)以及右斜线方向上的占用情况(`right_diag`)。全局变量`n`, `k`用来保存棋盘大小和要放置的国王数目,而`ans`则是最终答案的数量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值