4572: [Scoi2016]围棋 轮廓线DP KMP
Posted Cmd2001
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 }
もうこの手を 離さないから笑い合えるよ
我已不会再放手 所以一起欢笑吧
またこの場所から ふたり歩き出そう この道を
让我们再次从这里出发 踏上这条道路
朝の澄んだ陽射し 夜空に瞬く星
清晨干净的阳光 夜空中闪烁的繁星
たわいもないこと 分け合って感じるぬくもり
不管多琐碎的事 互相分享的温暖
ひとりきりの記憶 思い出してしまうたび
每当我回想起独自一人的记忆
いつも鄰で 撫でてくれてたから 笑えた
你总在我身边 抚摸着我朝我微笑
以上是关于4572: [Scoi2016]围棋 轮廓线DP KMP的主要内容,如果未能解决你的问题,请参考以下文章
[bzoj1084][SCOI2005]最大子矩阵_动态规划_伪·轮廓线dp
(轮廓线dp)UVA11270-Tiling Dominoes