AC自动机+状压dp
多串匹配要想ac自动机
dp[i][S]表示在i状态选中S
转移就用bfs,每个点通过fail收集信息,不要忘记通过fail传递
昨天搞不明白为什么自动机每次只可以转移儿子,不可以转移fail,问了问大概知道因为儿子是最长的后缀,包含的信息最多,包含了其他fail的信息,就相当于收集了其他fail的东西,就不用走了,但是一定要收集,这跟kmp很像,kmp就是走最大的后缀,这里也是,这样就可以保证不遗补漏同时加速
字典序最大在bfs时已经保证了,感觉比较显然,每次先拓展字典序最小的字符,bfs又求最短路,正好保证了字典序最小
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; const int N = 605; int n; char s[N]; namespace ac_automation { int cnt, root, tot; int id[N]; struct DP { short d, c; short pre[2]; DP () { d = -1; } } dp[N][1 << 12]; struct node { int fail; int ch[26]; } t[N]; void ins(char *s) { int len = strlen(s), now = root; for(int i = 0; i < len; ++i) { int c = s[i] - 'A'; if(!t[now].ch[c]) t[now].ch[c] = ++tot; now = t[now].ch[c]; } id[now] |= 1 << (cnt++); } void construct_fail() { queue<int> q; for(int i = 0; i < 26; ++i) if(t[root].ch[i]) q.push(t[root].ch[i]); while(!q.empty()) { int u = q.front(); q.pop(); for(int i = 0; i < 26; ++i) { int &v = t[u].ch[i]; if(!v) v = t[t[u].fail].ch[i]; else { t[v].fail = t[t[u].fail].ch[i]; id[v] |= id[t[v].fail]; q.push(v); } } } } void print(int u, int S) { if(u == root && S == 0) return; print(dp[u][S].pre[0], dp[u][S].pre[1]); printf("%c", (char)(dp[u][S].c + 'A')); } void solve() { int a = 0, b = 0, mn = 0x3f3f3f3f; queue<int> q; q.push(0); q.push(0); dp[0][0].d = 0; while(!q.empty()) { int u = q.front(); q.pop(); int S = q.front(); q.pop(); if(S == (1 << n) - 1) { if(dp[u][S].d < mn) { a = u; b = S; mn = dp[u][S].d; } continue; } for(int i = 0; i < 26; ++i) if(t[u].ch[i]) { int v = t[u].ch[i]; dp[v][S | id[v]].c = i; if(dp[v][S | id[v]].d == -1) { dp[v][S | id[v]].d = dp[u][S].d + 1; dp[v][S | id[v]].pre[0] = u; dp[v][S | id[v]].pre[1] = S; q.push(v); q.push(S | id[v]); } } } print(a, b); } } int main() { scanf("%d", &n); for(int i = 1; i <= n; ++i) { scanf("%s", s); ac_automation :: ins(s); } ac_automation :: construct_fail(); ac_automation :: solve(); return 0; }