zoukankan      html  css  js  c++  java
  • SCOI2016 围棋

    围棋

    近日,谷歌研发的围棋 AI —— AlphaGo 以 4 : 1 的比分战胜了曾经的世界冠军李世石,这是人工智能领域的又一里程碑。与传统的搜索式 AI 不同,AlphaGo 使用了最近十分流行的卷积神经网络模型。在卷积神经网络模型中,棋盘上每一块特定大小的区域都被当做一个窗口。例如棋盘的大小为 (5 imes 6),窗口大小为 (2 imes 4),那么棋盘中共有 (12) 个窗口。此外,模型中预先设定了一些模板,模板的大小与窗口的大小是一样的。

    对于一个模板,只要棋盘中有某个窗口与其完全匹配,我们称这个模板是被激活的,否则称这个模板没有被激活。我们要研究的问题是:对于给定的模板,有多少个棋盘可以激活它。为了简化问题,我们抛开所有围棋的基本规则,只考虑一个 $ n imes m $ 的棋盘,每个位置只能是黑子、白子或无子三种情况,换句话说,这样的棋盘共有 (3^{nm}) 种。此外,我们会给出 (q)(2 imes c) 的模板。我们希望知道,对于每个模板,有多少种棋盘可以激活它。

    强调:模板一定是两行的。


    题解

    考虑反面,用状压DP求出不合法的方案数,用(3^{n*m})减去它就行了。

    如果模板串只有一行,那么状态显然是(f[i][j]),表示长度为(i)匹配到状态(j)未出现模板串的方案数。

    考虑多了一行怎么做。显然不能两行都匹配上了,但是只有一行匹配上是可行的。那么就要记录二进制状态(S)表示上一行每个位置作为开头(或者结尾,一样的)是否完全匹配第一个串。为了方便转移(S),还要记录二进制状态(T)表示这一行每个位置作为开头是否匹配第一个串,以及当前位置匹配到了第一个串的哪个位置。显然(S)(T)不能有交集。

    那么设(f[i][j][S][T][x][y])表示填到了((i,j)),上一行每个位置作为开头是否完全匹配第一个串的状态为(S),这一行每个位置作为开头是否完全匹配第一个串的状态为(T),与第一个串kmp匹配到了(x),与第二个串kmp匹配到了(y)的方案数。然后直接加法转移即可。时空复杂度(O(nm 2^{2(m-c+1)} c^2)),显然不可行。

    比较显然的空间优化是滚动((i,j))。然后我们发现(S)(T)的有效状态位数之和一定是(m-c+1),所以这两维状态可以合并成一个。考虑这个合并后的状态的实际意义,发现它就长成人们口口相传的轮廓线的模样。这个比方对程序实现来说形象准确,但是对初学者来说难于理解。最后的小优化是用kmp预处理出自动机,加速匹配。

    时间复杂度(O(nm2^{m-c+1}c^2)),最后那个测试点是5e6.

    co int mod=1e9+7;
    int n,m,c,T,nxt[7],ta[6][3],tb[6][3],na,nb;
    char a[8],b[8];
    int id(char x) {return x=='B'?0:(x=='W'?1:2);}
    int U,f[1024][6][6],g[1024][6][6],ans;
    void up(int&x,int y) {x+=y;if(x>=mod)x-=mod;}
    void clear() {for(int S=0;S<U;++S)for(int x=0;x<c;++x)for(int y=0;y<c;++y)g[S][x][y]=0;}
    void copy() {for(int S=0;S<U;++S)for(int x=0;x<c;++x)for(int y=0;y<c;++y)f[S][x][y]=g[S][x][y];}
    int main(){
    	read(n),read(m),read(c),read(T);
    	while(T--){
    		scanf("%s%s",a+1,b+1);
    		for(int i=1;i<=c;++i)a[i]=id(a[i]),b[i]=id(b[i]);
    		for(int j=nxt[1]=0,i=2;i<=c;nxt[i++]=j){
    			while(j&&a[j+1]!=a[i]) j=nxt[j];
    			if(a[j+1]==a[i]) ++j;
    		}
    		na=nxt[c];
    		for(int i=0;i<c;++i)for(int j=0,k;j<3;++j){
    			for(k=i;k&&a[k+1]!=j;k=nxt[k]);
    			if(a[k+1]==j) ++k;
    			ta[i][j]=k;
    		}
    		for(int j=nxt[1]=0,i=2;i<=c;nxt[i++]=j){
    			while(j&&b[j+1]!=b[i]) j=nxt[j];
    			if(b[j+1]==b[i]) ++j;
    		}
    		nb=nxt[c];
    		for(int i=0;i<c;++i)for(int j=0,k;j<3;++j){
    			for(k=i;k&&b[k+1]!=j;k=nxt[k]);
    			if(b[k+1]==j) ++k;
    			tb[i][j]=k;
    		}
    		U=1<<(m-c+1);
    		for(int S=0;S<U;++S)for(int x=0;x<c;++x)for(int y=0;y<c;++y)f[S][x][y]=0;
    		for(int i=f[0][0][0]=1;i<=n;++i){
    			clear();
    			for(int S=0;S<U;++S)for(int x=0;x<c;++x)for(int y=0;y<c;++y)
    				if(f[S][x][y]) up(g[S][0][0],f[S][x][y]);
    			copy();
    			for(int j=1;j<=m;++j){
    				clear();
    				for(int S=0;S<U;++S)for(int x=0;x<c;++x)for(int y=0;y<c;++y)
    					if(f[S][x][y]) for(int k=0,A,B,E;k<3;++k){
    						E=S;
    						if(j>=c) if(S>>(j-c)&1) E^=1<<(j-c); // init
    						A=ta[x][k];
    						if(A==c) E|=1<<(j-c),A=na;
    						B=tb[y][k];
    						if(B==c){
    							if(S>>(j-c)&1) continue;
    							B=nb;
    						}
    						up(g[E][A][B],f[S][x][y]);
    					}
    				copy();
    			}
    		}
    		ans=1;
    		for(int i=n*m;i;--i) ans=3LL*ans%mod;
    		for(int S=0;S<U;++S)for(int x=0;x<c;++x)for(int y=0;y<c;++y) up(ans,mod-f[S][x][y]);
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    加强版

    由于那个状态(S)跟棋子无关,所以可以增加棋子种类。另外可以规定某些位置不能填什么,某些位置必须填什么。

  • 相关阅读:
    利用ansible一键部署nfs.yml格式剧本
    测试基础面试题 + SQL 面试题(选择题有部分答案,难度:低)
    Python Random模块
    SQL + Python 面试题:之二(难度:中等)
    接口测试面试题:之一(中英文题目、难度:简单)
    SQL面试题:之一(难度:中等)
    QA面试题:之一(中英文题目、难度:简单)
    通过 Python_Faker 生成测试数据
    Appium_Python_API_速查表
    Appium 命令使用说明
  • 原文地址:https://www.cnblogs.com/autoint/p/10533089.html
Copyright © 2011-2022 走看看