4572: [Scoi2016]围棋 轮廓线DP KMP

国际惯例的题面:

这种题目显然DP了,看到M这么小显然要状压。
然后就是具体怎么DP的问题。
首先我们可以暴力状压上一行状态,然后逐行转移。复杂度n*3^m+3^(m*2),显然过不去。

考虑状态的特殊性,每个位置是黑子白子我们并不关心,我们只关心与模板的匹配情况。
于是我们可以f(i,S,x,y)表示我们决策到i行j列,S表示上一行哪些位置和这一行哪些位置能与模板第一行完全匹配,x表示当前行与模板第一行匹配长度,y表示当前行与模板第二行匹配长度。
转移的话就枚举当前行下一个位置填什么颜色棋子(或空着)即可,复杂度n*(3^m)m*c*c*(2^m)*3,显然也凉了。
但是,我们发现如果一行能与模板第一行完全匹配,显然这个匹配位置最少在这一行的位置c。这样就能把次数中的m变成m-c+1。
这样仍旧不能AC。因为这只是普通的状压DP,显然有很多无用状态。

轮廓线DP的巧妙之处在于:因为采用了逐格转移,它压缩的状态可以部分是上一行的,部分是这一行的。
于是我们可以f(i,j,S,x,y)表示我们决策到i行j列,S表示上一行>=j的哪些位置和这一行<j的哪些位置能与模板第一行完全匹配,x表示当前行与模板第一行匹配长度,y表示当前行与模板第二行匹配长度。
我们枚举下一个格子填什么颜色的棋子,进行转移即可。复杂度n*m*3(m-c+1)*(c^2)*3。

代码:

 1 #include<cstdio>
 2 typedef long long int lli;
 3 const int maxs=1<<10,maxl=13;
 4 const int mod=1e9+7;
 5
 6 char ina[maxl],inb[maxl];
 7 int faila[maxl],failb[maxl],nxta[maxl][3],nxtb[maxl][3];
 8 int f[2][maxs][maxl][maxl];
 9 int n,m,c,q,full,mask,cur;
10 lli ans;
11
12 inline lli fastpow(lli base,int tim) {
13     lli ret = 1;
14     while(tim) {
15         ret = ( tim & 1 ) ? ret * base % mod : ret;
16         base = ( tim >>= 1 ) ? base * base % mod : base;
17     }
18     return ret;
19 }
20 inline char gid(char c) {
21     return c == ‘W‘ ? 0 : c == ‘B‘ ? 1 : 2;
22 }
23 inline void kmp(char* s,int* fail,int nxt[maxl][3]) {
24     for(int i=1;i<=c;i++) s[i] = gid(s[i]);
25     fail[0] = fail[1] = 0;
26     for(int i=2,j=0;i<=c;i++) {
27         while( j && s[j+1] != s[i] ) j = fail[j];
28         fail[i] = ( j += ( s[j+1] == s[i] ) );
29     }
30     for(int i=0;i<=c;i++) for(int cur=0;cur<3;cur++) {
31         int k = i;
32         while( k && s[k+1] != cur ) k = fail[k];
33         nxt[i][cur] = ( k += ( s[k+1] == cur ) );
34     }
35 }
36
37 inline void reset(int f[maxs][maxl][maxl]) {
38     for(int i=0;i<full;i++) for(int j=0;j<=c;j++) for(int k=0;k<=c;k++) f[i][j][k] = 0;
39 }
40
41 int main() {
42     scanf("%d%d%d%d",&n,&m,&c,&q) , full = 1 << ( m - c + 1 ) , mask = full - 1;
43     while(q--) {
44         scanf("%s%s",ina+1,inb+1) , kmp(ina,faila,nxta) , kmp(inb,failb,nxtb) , reset(f[cur=0]) , f[cur][0][0][0] = 1;
45         for(int i=1;i<=n;i++) {
46             reset(f[cur^=1]);
47             for(int j=0;j<full;j++) for(int pa=0;pa<c;pa++) for(int pb=0;pb<c;pb++) f[cur][j][0][0] = ( f[cur][j][0][0] + f[cur^1][j][pa][pb] ) % mod;
48             for(int j=1;j<=m;j++) {
49                 reset(f[cur^=1]);
50                 for(int sta=0;sta<full;sta++) for(int pa=0;pa<c;pa++) for(int pb=0;pb<c;pb++) if( f[cur^1][sta][pa][pb] )for(int sel=0;sel<3;sel++) {
51                     int nowa = nxta[pa][sel] , nowb = nxtb[pb][sel] , nowsta = sta;
52                     if( j >= c ) nowsta &= ( mask ^ ( 1 << ( j - c ) ) ); // clear bit j - c .
53                     if( nowa == c ) nowsta ^= 1 << ( j - c ) , nowa = faila[nowa]; // set bit j - c .
54                     if( nowb == c ) {
55                         if( sta & ( 1 << ( j - c ) ) ) continue; // paired .
56                         else nowb = failb[nowb];
57                     }
58                     f[cur][nowsta][nowa][nowb] = ( f[cur][nowsta][nowa][nowb] + f[cur^1][sta][pa][pb] ) % mod;
59                 }
60             }
61         }
62         ans = fastpow(3,n*m);
63         for(int sta=0;sta<full;sta++) for(int pa=0;pa<c;pa++) for(int pb=0;pb<c;pb++) ans = ( ans - f[cur][sta][pa][pb] + mod ) % mod;
64         printf("%lld\n",ans);
65     }
66     return 0;
67 }

もうこの手を 離さないから笑い合えるよ
我已不会再放手 所以一起欢笑吧
またこの場所から ふたり歩き出そう この道を
让我们再次从这里出发 踏上这条道路
朝の澄んだ陽射し 夜空に瞬く星
清晨干净的阳光 夜空中闪烁的繁星
たわいもないこと 分け合って感じるぬくもり
不管多琐碎的事 互相分享的温暖
ひとりきりの記憶 思い出してしまうたび
每当我回想起独自一人的记忆
いつも鄰で 撫でてくれてたから 笑えた
你总在我身边 抚摸着我朝我微笑

原文地址:https://www.cnblogs.com/Cmd2001/p/8988126.html

时间: 2024-10-13 06:12:36

4572: [Scoi2016]围棋 轮廓线DP KMP的相关文章

BZOJ4572: [Scoi2016]围棋

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

POJ 3254 Corn Fields (状压DP,轮廓线DP)

题意: 有一个n*m的矩阵(0<n,m<=12),有部分的格子可种草,有部分不可种,问有多少种不同的种草方案(完全不种也可以算1种,对答案取模后输出)? 思路: 明显的状压DP啦,只是怎样压缩状态?跟轮廓线DP一样,按格子为单位来设计状态,一个状态只需要表示到其上方和左方的格子,所以最多只需要保存min(n,m)个01状态就行了(可以尝试旋转一下矩阵),最多需要12位.用哈希表来做会比较快吧,不用去考虑无效的状态,比如出现相邻两个1. 1 //#include <bits/stdc++.

轮廓线DP POJ3254

补了一发轮廓线DP,发现完全没有必要从右往左设置状态,自然一点: 5 6 7 8 9 1 2 3 4 如此设置轮廓线标号,转移的时候直接把当前j位改成0或者1就行了.注意多记录些信息对简化代码是很有帮助的,尤其对于我这种代码经常错的一塌糊涂的人来说.. 呆马: #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cmath>

轮廓线dp (插头dp)

轮廓线dp 骨牌覆盖问题 n和m比较小 1 #pragma comment(linker, "/STACK:102400000,102400000") 2 3 #include <cstdio> 4 #include <cstdlib> 5 #include <cstring> 6 #include <cmath> 7 #include <ctime> 8 #include <iostream> 9 #includ

POJ 2411 Mondriaan&#39;s Dream( 轮廓线dp )

最普通的轮廓线dp... 复杂度O(nm2min(n, m)) -------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; #define b(x) (1 << (x)) const in

hdu 5766 Filling 轮廓线dp burnside

Filling 题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=5766 Description The board is a rectangle of unit cells with N rows and N columns. At first, cells are empty. ?? has infinite blocks with the size of 2*2. ?? can put blocks in the board as long

UVa 11270 铺放骨牌(轮廓线DP)

https://vjudge.net/problem/UVA-11270 题意: 用1×2骨牌覆盖n×m棋牌,有多少种方法? 思路: 这道题目是典型的轮廓线DP题. 所谓轮廓线DP,就是以整行整列为状态进行动态规划时无法进行状态转移,那么此时就可以用到轮廓线,当然,这种方法只能使用在一个窄棋盘上,大了肯定是不行的,要超时! ' 轮廓线DP就是按照从上到下,从左到右的顺序进行状态转移,每个格子用二进制来表示状态,1代表的就是覆盖,0代表未覆盖. 以本题为例,如上图,我们现在要计算 k 格子,那么与

HDU4949 Light (轮廓线dp)

题意:给你一个01矩阵,有两种操作: 第一种: 把a(i,j)的周围四个都异或一下 第二种: 把a(i, j)的周围四个和a(i,j)都异或一下 求把矩阵变成全0矩阵的最少操作次数 思路:如下图所示的轮廓线dp,逐格递推的,cur为当前决策的格子,红色线就是轮廓线,轮廓线以上的格子的操作状态都已经确定了,而对下面状态有影响的只有黄色格子,每个格子保存的是格子当前的数和它自己操作了多少次,因为无论是1还是2操作,对其他格子的影响都是一样的,这样子的复杂度是O(n*m*4^10),明显是会超时的.然

poj 2411 Mondriaan&#39;s Dream (轮廓线DP)

题意:有一个n*m的棋盘,要求用1*2的骨牌来覆盖满它,有多少种方案?(n<12,m<12) 思路: 由于n和m都比较小,可以用轮廓线,就是维护最后边所需要的几个状态,然后进行DP.这里需要维护的状态数就是min(n,m).即大概是一行的大小.每次放的时候,只考虑(1)以当前格子为右方,进行横放:(2)以当前格子为下方进行竖放:(3)还有就是可以不放. 3种都是不一样的,所以前面的一种状态可能可以转为后面的几种状态,只要满足了条件.条件是,横放时,当前格子不能是最左边的:竖放时,当前格子不能是