题解 P2322 [HNOI2006]最短母串问题

$\texttt{Link}$

ACAM 和状压 dp 的做法

题意

给定 $n$ 个字符串 $s_1,s_2,s_3...s_n$,对于字符串 $T$,满足 $\forall i\in\left[1,n\right]$,$s_i$ 为 $T$ 的子串,求 $|T|$ 最小时的 $T$。

数据范围: $1\le n\le12,1\le s_i\le50$

做法

显然是个多模匹配问题,考虑 ACAM。

若使 $T$ 匹配到所有字符串,说明该 $T$ 在 Trie 图上包含了所有字符串的结束节点。又因为每个节点到根的路径上可能会包含很多个字符串,所以状态压缩存每个节点到跟的路径上包含的字符串,对于节点 $i$,记 $sta_i$ 存该点路径上包含的字符串,令 $2^{i-1}$ 表示 $s_i$,即用二进制表示。若 $s_i$ 结束的节点为 $p$,则 $sta_p\gets sta_p\operatorname{or} 2^{i-1}$,即 $sta_p$ 加入字符串 $s_i$。在构建 $fail$ 树的时候再顺着 $fail$ 边加入所有后缀的状态。

在寻找 $\min |T|$ 时,从根节点按字典序 BFS,记录当前状态 $now$,顺着 Trie 树,每到一个节点 $x$,令 $now\gets now \operatorname{or}sta_x$,并用 $pre_x$ 指针记录该次 BFS 时间,直到 $now=2^n-1$,即每个字符串都在该串中,又因为是 BFS,该串长度最小,即为答案,利用 $pre$ 指针输出即可。

时间复杂度 $O(2^n\sum |s_i|)$。

#include <bits/stdc++.h>
#define pii pair<int, int>
using namespace std;
const int N = 6e2 + 10, M = 1 << 12 | 1, S = 26;
int n, ans[N * M], tot, pre[N * M];
char s[N];
bool vis[N][M];
namespace AC_Automaton {
    int ch[N][S], fail[N], sta[N], cnt;
    void insert(char *s, int d) {
        int len = strlen(s + 1), x = 0;
        for(int i = 1; i <= len; i++) {
            if(!ch[x][s[i] - 'A']) ch[x][s[i] - 'A'] = ++cnt;
            x = ch[x][s[i] - 'A'];
        }
        sta[x] |= (1 << (d - 1));
    }
    void getfail() {
        queue<int> q;
        for(int i = 0; i < S; i++) if(ch[0][i]) q.push(ch[0][i]);
        while(!q.empty()) {
            int u = q.front();
            q.pop();
            for(int i = 0; i < S; i++)
                if(ch[u][i]) fail[ch[u][i]] = ch[fail[u]][i], sta[ch[u][i]] |= sta[ch[fail[u]][i]], q.push(ch[u][i]);
                else ch[u][i] = ch[fail[u]][i];
        }
    }
} using namespace AC_Automaton;
queue<pii> q;
int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%s", s + 1);
        insert(s, i);
    }
    getfail();
    q.push({0, 0});
    vis[0][0] = 1;//vis判重,第一维表示节点,第二维表示状态 
    int now = 0;
    while(!q.empty()) {
        int x = q.front().first, st = q.front().second;
        q.pop();
        if(st == (1 << n) - 1) {
            int len = 0;
            while(now) {
                s[++len] = ans[now] + 'A';
                now = pre[now];
            }
            for(int i = len; i; i--) putchar(s[i]);
            break;
        }
        for(int i = 0, v; i < S; i++) {
            v = ch[x][i];
            if(!vis[v][st | sta[v]]) {
                vis[v][st | sta[v]] = 1;
                q.push({v, st | sta[v]});
                pre[++tot] = now, ans[tot] = i;
            }
        }
        now++;
    }
    return 0;
}
posted @ 2021-07-13 14:44  Terac  阅读(23)  评论(0)    收藏  举报  来源