[JSOI2009]密码 AC自动机
题面:洛谷
题解:
我们对给定串建AC自动机,因为串个数较小,我们考虑状压。
设f[i][j][k]表示走了i步,当前在j号节点上,状态为k的方案数。
同时AC自动机上每个点的val都是对应的到达这个点后可以新增的状态。
每次枚举下一个字符即可转移,在建完自动机之后,记得把fail树上对应的链上的点的val给继承一下。
因为一个串已经出现了,那么就代表它的子串也已经出现了,作为它的子串,在AC自动机上就表现为跳fail可以到达的地方。
应该我们建fail的时候已经有一个bfs序了,所以也可以直接利用这个bfs序按顺序下传val,这样可以快一点(详见代码)
题目的要求是要求构建出来的串要使得所有给定串都是这个串的子串。
但是在方案小于等于42时,还要求输出方案。因此我们可以记忆化搜索一下,处理出哪些状态是最终对ans产生了贡献的状态(间接贡献也算)
然后在AC自动机上,爆搜,只走这些产生了贡献的状态,这样可以保证只搜到合法解。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define LL long long 5 #define AC 300 6 #define ac 1050 7 8 int n, m, go, maxn, tot, head, tail; 9 int c[AC][26], fail[AC], val[AC], p[AC], q[ac]; 10 LL f[30][AC][ac], ans;//f[i][j][k]表示已经走了i步,当前在第j个节点上,状态为k的方案数 11 char s[AC], vis[30][AC][ac]; 12 13 inline int read() 14 { 15 int x = 0;char c = getchar(); 16 while(c > '9' || c < '0') c = getchar(); 17 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 18 return x; 19 } 20 21 inline void add() 22 { 23 int len = strlen(s + 1), now = 0; 24 for(R i = 1; i <= len; i ++) 25 { 26 int v = s[i] - 'a'; 27 if(!c[now][v]) c[now][v] = ++ tot; 28 now = c[now][v]; 29 } 30 val[now] |= p[++ go]; 31 } 32 33 inline void build() 34 { 35 for(R i = 0; i < 26; i ++) if(c[0][i]) q[++ tail] = c[0][i]; 36 while(head < tail) 37 { 38 int x = q[++ head]; 39 for(R i = 0; i < 26; i ++) 40 { 41 if(c[x][i]) fail[c[x][i]] = c[fail[x]][i], q[++ tail] = c[x][i]; 42 else c[x][i] = c[fail[x]][i]; 43 } 44 } 45 for(R i = 1; i <= tot; i ++) val[i] |= val[fail[i]]; 46 } 47 48 inline void DP() 49 { 50 f[0][0][0] = 1; 51 //fail[8] = 14; 52 for(R i = 0; i < n; i ++)//枚举当前走了多少步 53 for(R j = 0; j <= tot; j ++) //枚举当前在哪个点上 54 for(R k = 0; k <= maxn; k ++)//枚举当前状态 55 { 56 if(f[i][j][k]) 57 for(R l = 0; l < 26; l ++)//枚举下一个字符 58 { 59 int v = c[j][l]; 60 f[i + 1][v][k | val[v]] += f[i][j][k]; 61 } 62 } 63 } 64 65 int sta[AC], top; 66 void dfs(int x, int y, int z)//已经走了x步,当前在第y个节点上,状态为z 67 { 68 if(x == n) 69 { 70 for(R i = 1; i <= n; i ++) printf("%c", sta[i] + 'a'); 71 puts(""); return ; 72 } 73 for(R i = 0; i < 26; i ++) 74 { 75 int v = c[y][i]; 76 if(vis[x + 1][v][z | val[v]] != '1') continue; 77 sta[++ top] = i; 78 dfs(x + 1, v, z | val[v]); 79 -- top; 80 } 81 } 82 83 inline void pre() 84 { 85 n = read(), m = read(), maxn = (1 << m) - 1; 86 LL tmp = 1; 87 for(R i = 1; i <= m; i ++) p[i] = tmp, tmp <<= 1;//要先预处理 88 for(R i = 1; i <= m; i ++) scanf("%s", s + 1), add(); 89 } 90 91 bool dfs1(int x, int y, int z)//当前在第x位, 在节点y, 状态为z 92 { 93 if(vis[x][y][z] == '1') return true;//1表示可以 94 else if(vis[x][y][z] == '2') return false;//2表示不行 95 else if(x == n) {vis[x][y][z] = '2'; return false;} 96 for(R i = 0; i < 26; i ++) 97 { 98 int v = c[y][i]; 99 if(dfs1(x + 1, v, z | val[v])) vis[x][y][z] = '1'; 100 } 101 if(vis[x][y][z] != '1') {vis[x][y][z] = '2'; return false;}; 102 return true; 103 } 104 105 inline void work() 106 { 107 for(R i = 0; i <= tot; i ++) 108 if(f[n][i][maxn]) ans += f[n][i][maxn], vis[n][i][maxn] = '1'; 109 else vis[n][i][maxn] = '2'; 110 printf("%lld\n", ans); 111 if(ans <= 42) 112 { 113 for(R i = 0; i < n; i ++)//枚举当前走了多少步 114 for(R j = 0; j <= tot; j ++) //枚举当前在哪个点上 115 for(R k = 0; k <= maxn; k ++)//枚举当前状态 116 if(f[i][j][k]) dfs1(i, j, k); 117 dfs(0, 0, 0); 118 } 119 } 120 121 int main() 122 { 123 // freopen("in.in", "r", stdin); 124 pre(); 125 build(); 126 DP(); 127 work(); 128 // fclose(stdin); 129 return 0; 130 }