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
",ans);
65     }
66     return 0;
67 }
View Code

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

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