AC自动机

Posted 1625--h

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AC自动机相关的知识,希望对你有一定的参考价值。

AC 自动机

1. Dominating Patterns

UVA - 1449

给N个串,然后再给一个串s,求N个串总共在S中出现了多少次

将N个串插入到AC自动机当中,如果某个结点为模式串末尾结点,则在fail树中,以该节点为祖先的所有结点,贡献都+1

然后直接暴力匹配即可,最后倒着去算一遍贡献。(具体体现在fail树中,如果一个结点匹配上了,那么他的祖先一定都可以匹配上,按序号降序去累加可以保证顺序是正确的,因为一个结点的fail指针所指结点的序号一定更小)

需要注意的是,N个串中可能有相同的。

const int N = 1000010 + 5;
int n;
char s[N], P[200][100];
map<string,int> mp;
namespace AC{
    int tr[N][26],tot;
    int e[N],fail[N];
    int cnt[N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = cnt[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i] - 'a']){
                tr[u][s[i] - 'a'] = ++tot;
            }
            u = tr[u][s[i] - 'a'];
        }
        mp[string(s+1)] = u;
        e[u] ++;
    }
    void build(){
        for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void get(char *t){
        int u = 0,res = 0;
        for(int i=1;t[i];i++){
            u = tr[u][t[i] - 'a'];
            cnt[u] ++;
        }
        for(int i=tot;i>=0;i--) cnt[fail[i]] += cnt[i];
        for(int i=0;i<=tot;i++) if(e[i]){
            res = max(res, cnt[i]);
        }
        printf("%d
", res);
        for(int i=1;i<=n;i++){
            if(cnt[mp[string(P[i]+1)]] == res) printf("%s
", P[i]+1);
        }
    }
}


int main() {
    while(~scanf("%d",&n)){
        if(n == 0) break;
        mp.clear();
        AC::init();
        for(int i=1;i<=n;i++){
            scanf("%s", P[i]+1);
            AC::insert(P[i]);
        }
        AC::build();
        scanf("%s",s+1);
        AC::get(s);
    }
    return 0;
}

2. Substring

UVA - 11468

给 K 个串,再给一个大小为N的字符集,给出从N个字符中选第 i 个的概率 p_i, 然后每次从中选出一个字符,选L次拼成一个长为L的字符串,求K个串不出现在该串中的概率。

AC自动机中的trans函数,即状态转移函数,如果 (trans[u][c]) 所指向的结点是一个字符串的末尾结点或者是末尾结点在fail树上的子孙,那么该转移不能选择,如果选择则意味着有一个字符串会出现在构造的这个串中。

到这里就变成了一个概率DP的问题,为了降低编程难度,可以直接用记忆化搜索实现。

还需要注意的是字符集的问题,它包括了大小写以及数字。

const int N = 1010;
int n, k, L, vis[1010][200], in[300];
double p[300], d[1010][200];
char s[22][22];
namespace AC{
    int tr[N][300],tot;
    int e[N],fail[N];
    int cnt[N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = cnt[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i]]){
                tr[u][s[i]] = ++tot;
            }
            u = tr[u][s[i]];
        }
        e[u] ++;
    }
    void build(){
        for(int i=0;i<130;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] |= e[fail[u]]; // e[u]=1则表示u结点所表示的状态的某一个后缀对应K个字符串中的某个
            for(int i=0;i<130;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    double get(int u, int L){
        if(!L) return 1.0;
        if(vis[u][L]) return d[u][L];
        vis[u][L] = 1;
        double& ans = d[u][L];
        ans = 0;
        for(int i=0;i<130;i++){
            //in[i] 则表示该字符出现在题目给定的字符集中
            if(in[i] && !e[tr[u][i]]) ans += p[i] * get(tr[u][i], L-1);
        }
        return ans;
    }
}
int main() {
    int T;scanf("%d",&T);
    int cas = 0;
    while(T--){
        memset(vis, 0, sizeof vis);
        memset(in, 0, sizeof in);
        AC::init();
        scanf("%d",&k);
        for(int i=1;i<=k;i++){
            scanf("%s", s[i]+1);
            AC::insert(s[i]);
        }
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            char op[10];scanf("%s", op);
            scanf("%lf", &p[op[0]]);
            in[op[0]] = 1;
        }
        AC::build();
        scanf("%d",&L);
        printf("Case #%d: %.6f
", ++cas, AC::get(0, L));
    }
    return 0;
}

3. Matrix Matcher

UVA - 11019

题意:给出一个(n*m)的字符矩阵(T),你的任务是找出给定的(x*y) 的字符矩阵(P) 出现了多少次。

分析:将 (P) 的每一行加入到AC自动机中,然后对于(T) 的每一行,去自动机中暴力匹配,如果找到了 (T) 的第 (row) 行的第 (i) 个位置匹配到了 (P)(j)行字符的最后一个位置,说明在 (T[row][i-y+1]) 开始的位置可以匹配 P 的 (j) 行。这样,以 (T[row-j+1][i-y+1]) 为左上角的,大小为(x*y) 的子矩阵中,新增加了一行可以与 (P) 匹配,这个信息用(match[i][j]) 来存放,每次遇到使其加一。

最后跑一遍,满足(match[i][j] = x) 的那些((i,j)) 就是一个二位匹配点。

const int N = 100000 + 5;
int n, m, x, y;
char s[1010][1010], t[110][110];
int match[1010][1010];
namespace AC{
    int tr[N][26],tot;
    int e[N],fail[N];
    int cnt[N];
    vector<int> ends[N]; 
    queue<int> q;
    void init(){
        memset(match, 0, sizeof match);
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = cnt[i] = 0;
            ends[i].clear();
        }
        tot = 0;
    }
    void insert(char *s, int pos){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i] - 'a']){
                tr[u][s[i] - 'a'] = ++tot;
            }
            u = tr[u][s[i] - 'a'];
        }
        e[u] ++;
        ends[u].push_back(pos); // 将行序号加到对应结点末尾
    }
    void build(){
        for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            for(int i=0;i<26;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void solve(){
        for(int row=1;row<=n;row++){
            int u = 0;
            for(int i = 1; i <= m; i++){
                u = tr[u][s[row][i] - 'a'];
                //这里并不需要跳fail,因为这些模式串的长度都一定
                for(auto pos : ends[u]){
                    match[row - pos + 1][i - y + 1] ++;
                }
            }
        }
        int res = 0;
        for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(match[i][j] == x) res++;
        printf("%d
", res);
    }
}

int main() {
    int T;scanf("%d",&T);
    while(T--){
        AC::init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%s",s[i]+1);
        }
        scanf("%d%d",&x,&y);
        for(int i=1;i<=x;i++){
            scanf("%s",t[i]+1);
            AC::insert(t[i], i);
        }
        AC::build();
        AC::solve();
    }
    return 0;
}

4. Detect the Virus

ZOJ - 3430

比较恶心的多模式串匹配,需要预处理输入,将base64转换为uchar类型

const int inf = 0x3f3f3f3f;
const int N = 100000 + 5;
char cb64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int id[200], a[100000];
char s[10000];
int t[10000];
int n;
void init(){
    for(int i=0;i<64;i++){
        id[cb64[i]] = i;
    }
}
void pushback(int *a, int &pos, int x){
    for(int i=0;i<6;i++){
        a[pos++] = x >> (5-i) & 1;
    }
}
int convert(char * s, int *t){ // s为base64,t为二进制
    int len = strlen(s), pos = 0, top = 0;
    for(int i=0;i<len;i++){
        if(s[i] == '=')continue;
        pushback(a,pos, id[s[i]]);
    }
    int x = 0;
    for(int i=0;i<pos;i++){
        x = x * 2 + a[i];
        if((i+1) % 8 == 0){
            t[top++] = x;
            x = 0;
        }
    }
    return top;
}
namespace AC{
    int tr[N][256],tot;
    int e[N],fail[N];
    int st[N], top, v[N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(int *s, int len){
        int u = 0;
        for(int i=0;i<len;i++){
            if(!tr[u][s[i]]){
                tr[u][s[i]] = ++tot;
            }
            u = tr[u][s[i]];
        }
        e[u] ++;
    }
    void build(){
        for(int i=0;i<256;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            for(int i=0;i<256;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void solve(int *s, int len){
        int res = 0, u = 0;
        for(int i=0;i<len;i++){
            u = tr[u][s[i]];
            for(int j=u; j && !v[j]; j=fail[j]){
                res += e[j];
                st[++top] = j;
                v[j] = 1;
            }
        }
        for(int i=1;i<=top;i++)v[st[i]] = 0;
        top = 0;
        printf("%d
", res);
    }
}
int main(){
    init();
    while(~scanf("%d",&n)){
        AC::init();
        for(int i=1;i<=n;i++){
            scanf("%s",s);
            int len = convert(s, t);
            AC::insert(t, len);
        }
        AC::build();
        scanf("%d",&n);
        while(n--){
            scanf("%s",s);
            int len = convert(s, t);
            AC::solve(t, len);
        }
        puts("");
    }
    return 0;
}

5. [P2414 NOI2011]阿狸的打字机

不难想到每个字符串结尾结点在fail树上的子孙代表的含义,对于一个询问((x,y)) ,要求的就是 x 号字符串的末尾结点的子树中,有多少个结点可以匹配到 y 中。

暴力的把所以标记打到fail树上?N个字符串,每个字符串长度不固定,有可能都很长,显然不行。每次对单个询问处理貌似都不太合适,可以把询问离线,集中处理询问。

先将fail树构建dfs序列,那么询问某个结点的子树情况可以转换为一个序列问题,接下来可以dfs扫描原Trie树(不是AC自动机构建出来的字典图),每次扫描到一个结点时,若从根节点到该结点的路径表示为询问中的 y,那么扫描它的所有询问 x,在fail树dfs序上找答案即可。

#define mk make_pair
const int N = 100000 + 5;
char s[N];
int n, m;
vector<pair<int,int>> query[N];
int c[N], res[N], pos[N];
void add(int x, int y){
    for(;x<N;x+=x&-x) c[x]+=y;
}
int ask(int x){
    int res = 0;
    for(;x;x-=x&-x) res+=c[x];
    return res;
}
namespace AC{
    int tr[N][26],tot, trie[N][26];
    int e[N], fail[N], fa[N];
    int u;
    int dfn[N], sz[N], cnt;
    vector<int> ends[N];
    vector<int> v[N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
        u = 0;
    }
    void insert(char c){
        if(!tr[u][c-'a'])tr[u][c-'a'] = ++tot,fa[tot] = u;
        u = tr[u][c-'a'];
    }
    void setend(int id){
        e[u] ++;
        ends[u].push_back(id);
        pos[id] = u;
    }
    void getup(){
        if(u != 0)
            u = fa[u];
    }
    void insert(char *s){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i] - 'a']){
                tr[u][s[i] - 'a'] = ++tot;
            }
            u = tr[u][s[i] - 'a'];
        }
        e[u] ++;
    }
    void build(){
        for(int i=0;i<=tot;i++) memcpy(trie[i], tr[i], sizeof tr[i]);//保留原Trie树
        for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            if(u) v[fail[u]].push_back(u);//构建fail树
            for(int i=0;i<26;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void dfs(int u){//构建fail树dfs序列
        dfn[u] = ++cnt;
        sz[u] = 1;
        for(auto x : v[u]){
            dfs(x);
            sz[u] += sz[x];
        }
    }
    void solve(int u){
        add(dfn[u], 1);
        for(auto y : ends[u]){
            for(auto t : query[y]){
                int x = t.first;
                int id = t.second;
                int dfnid = dfn[pos[x]];
                res[id] = ask(dfnid + sz[pos[x]] - 1) - ask(dfnid-1);
            }
        }
        for(int i=0;i<26;i++){
            if(trie[u][i])solve(trie[u][i]);
        }
        add(dfn[u], -1);
    }
}


int main() {
    scanf("%s",s);
    AC::init();
    int len = strlen(s);
    n = 0;
    for(int i=0;i<len;i++){
        if(s[i] == 'B')AC::getup();
        else if(s[i] == 'P'){
            AC::setend(++n);
        }else AC::insert(s[i]);
    }
    AC::build();
    AC::dfs(0);
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        query[y].push_back(mk(x, i));//离线询问
    }
    AC::solve(0);
    for(int i=1;i<=m;i++) printf("%d
", res[i]);
    return 0;
}

6. DNA repair

HDU - 2457

AC自动机上面DP即可,注意要记忆化以下

const int N = 1000 + 5;
int n;
char s[N];
int xid[26];
namespace AC{
    int tr[N][4],tot;
    int e[N],fail[N];
    int vis[N][N], d[N][N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(vis[i], 0, sizeof vis[i]);
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            int c = xid[s[i] - 'A'];
            if(!tr[u][c]){
                tr[u][c] = ++tot;
            }
            u = tr[u][c];
        }
        e[u] ++;
    }
    void build(){
        for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] |= e[fail[u]];
            for(int i=0;i<4;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    int get(int u, int pos, int len){
        if(e[u]) return inf;//这个要放在最前面,u肯定是合法结点(不会超过tot),由于我第一次写好没放最前面,debug好久
        if(pos == len + 1) return 0;
        if(vis[u][pos]) return d[u][pos];
        vis[u][pos] = 1;
        int &res = d[u][pos];
        res = inf;
        int c = xid[s[pos] - 'A'];
        for(int i=0;i<4;i++){
            res = min(res, (i != c) + get(tr[u][i], pos+1, len));
        }
        return res;
    }
}


int main() {
    xid['A' - 'A'] = 0;
    xid['G' - 'A'] = 1;
    xid['C' - 'A'] = 2;
    xid['T' - 'A'] = 3;
    int cas = 0;
    while(scanf("%d",&n) != EOF){
        if(n == 0) break;
        AC::init();
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            AC::insert(s);
        }
        AC::build();
        scanf("%s",s+1);
        int res = AC::get(0, 1, strlen(s+1));
        if(res == inf) res = -1;
        printf("Case %d: %d
",++cas, res);
    }
    return 0;
}

7. Ring

HDU - 2296

同样也是AC自动机上面DP,其实很好做的题,肯定是由于出题人懒得写spj导致一堆特殊条件

(d[u][i]) 表示从 (u) 结点出发,走 (i) 的长度,最多可以匹配多少价值,(p[u][i]) 表示路径。

先求出(d[0][n]), 然后找一个最小的 i , 使得出(d[0][i] == d[0][n]),然后按照路径输出。

由于枚举时是按照字典序从小到大的,所以这里直接输出即可

const int N = 2000 + 5;
const int M = 55;
char s[N];
int n, m, pos[N];
namespace AC{
    int tr[N][26],tot;
    int e[N],fail[N];
    int vis[N][M], d[N][M], p[N][M];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(vis[i], 0, sizeof vis[i]);
            memset(tr[i],0,sizeof tr[i]);
            memset(p, 0, sizeof p);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s, int id){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            int c = s[i] - 'a';
            if(!tr[u][c]){
                tr[u][c] = ++tot;
            }
            u = tr[u][c];
        }
        pos[id] = u;
    }
    void build(){
        for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] += e[fail[u]];//权值累加
            for(int i=0;i<26;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    int get(int u, int n){ // 从 u 开始 n 长度
        if(n == 0) return 0;
        if(vis[u][n]) return d[u][n]; // 记忆化搜索
        vis[u][n] = 1;
        int &ans = d[u][n];
        ans = 0;
        for(int i=0;i<26;i++){
            int c = tr[u][i];
            int now = e[c] + get(c, n - 1);
            if(now > ans){ // 必须为大于号,因为答案要求字典序最小
                ans = now;
                p[u][n] = i;
            }
        }
        return ans;
    }
    void print(int u, int n){
        if(n == 0) return;
        int c = p[u][n];
        printf("%c", char('a'+c));
        print(tr[u][c], n-1);
    }
}

int main() {
    int T;
    scanf("%d",&T);
    while(T--){
        AC::init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            scanf("%s",s+1);
            AC::insert(s, i);
        }
        for(int i=1;i<=m;i++){
            scanf("%d", &AC::e[pos[i]]);
        }
        AC::build();
        AC::get(0, n);
        for(int i=0;i<=n;i++){
            if(AC::get(0, i) == AC::get(0, n)){ //这里一定要用这种方式访问dp值!!!,由于是记忆化搜索所以再调用函数之前不一定会有d[0][i],被这里坑了
                n = i;
                break;
            }
        }
        AC::print(0,n);
        puts("");
    }
    return 0;
}

8. Searching the String

ZOJ - 3228

暴力跳(fail)进行统计。每次跳到一个结点$ j$,表示该结点表示的子串在 (s) 中出现了一次,对于第一类询问,直接++,对于第二类询问,需要利用上一次该位置在 (s) 中出现的位置(last),如果 (i - last >= len(j)),则表示两次出现在 (s) 中不交叉,则答案++.

因为本身模式串的长度最长才6,暴力跳fail几乎对时间没有影响。但如果还想优化,可以这样考虑,每次暴力跳fail的结果不一定会对计算结果有用,因为有一些结点并不是末尾结点,为了防止跳到这些没有用的结点,再用一个(pre) 数组来记录,在(bfs) 的过程中可以这么计算:

if(e[fail[u]]) pre[u] = fail[u];

else pre[u] = pre[fail[u]];

实际优化不是很明显:

技术图片

const int N = 600000 + 5;
const int M = 100010;
int n;
char s[M], t[10];
int tp[M], res[N][2], last[N], dep[N], pos[M];
namespace AC{
    int tr[N][26],tot;
    int e[N],fail[N], pre[N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            res[i][0] = res[i][1] = 0;
            fail[i] = e[i] = 0;
            last[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s, int id){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i] - 'a']){
                tr[u][s[i] - 'a'] = ++tot;
            }
            u = tr[u][s[i] - 'a'];
        }
        e[u] ++;
        dep[u] = len;
        pos[id] = u;
    }
    void build(){
        for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            if(e[fail[u]]) pre[u] = fail[u];
            else pre[u] = pre[fail[u]];
            for(int i=0;i<26;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void get(){
        int len = strlen(s+1);
        int u = 0;
        for(int i=1;i<=len;i++){
            int c = s[i] - 'a';
            u = tr[u][c];
            for(int j = u; j; j = pre[j]){
                if(e[j]){
                    res[j][0] ++;
                    if(i - last[j] >= dep[j]){
                        last[j] = i;
                        res[j][1] ++;
                    }
                }
            }
        }
    }
}
int main() {
    int cas = 0;
    while(~scanf("%s",s+1)){
        AC::init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d%s",&tp[i], t+1);
            AC::insert(t, i);
        }
        AC::build();
        AC::get();
        printf("Case %d
", ++cas);
        for(int i=1;i<=n;i++){
            printf("%d
", res[pos[i]][tp[i]]);
        }
        puts("");
    }
    return 0;
}

9. Frequency of String

http://codeforces.com/problemset/problem/963/D

给出一个字符串S, n次询问,每次询问给出正整数k和字符串m, 要求字符串T的最小长度,满足T是S的子串,且m在T中出现了至少K次。

首先想一下暴力做法:将所有询问串构建AC自动机,然后在上面跑S,若在(s[i]) 匹配到了一个串,那么就给这个串添加一个 i 位置为结尾的出现。最后进行统计即可(连续 k 次出现的最小长度)。

每次匹配到一个位置几乎都要暴力跳 fail,因为这个题目中多模式串的长度有长有短,很容易出现某个串是另外一个串的后缀这种情况,所以很容易T。但根据上一道题目中提到的小优化进行优化呢?每次只给对答案有用的结点添加出现位置,那最终添加的次数就是每个询问串在 s 中的所有出现位置。似乎还是会有很多。

注意到,题目中说明了每个询问串都不一样。对于长度一样的询问串,在S中最多有(|S|) 次出现,而对于总长度为(sum|m|) 的多模式串来讲,(|m|) 最多有(sqrt{sum|m|}) 种,也就是将(sum|m|) 最多分成(sqrt{sum|m|})个不同的数字((sum = frac{n*(n+1)}{2})) (n) 差不多在(sqrt{sum}) 级别。

所以最后最多会添加(sqrt{sum|m|} imes|S|) 次位置。

const int N = 100000 + 5;
int n;
char s[N], t[N];
vector<int> pos[N];
int p[N], k[N], len[N];
namespace AC{
    int tr[N][26],tot;
    int e[N], fail[N], pre[N];
    queue<int> q;
    void insert(char *s, int id){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i] - 'a']){
                tr[u][s[i] - 'a'] = ++tot;
            }
            u = tr[u][s[i] - 'a'];
        }
        e[u] ++;
        p[u] = id;
    }
    void build(){
        for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            if(e[fail[u]]) pre[u] = fail[u];
            else pre[u] = pre[fail[u]];
            for(int i=0;i<26;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void get(){
        int len = strlen(s+1);
        int u = 0;
        for(int i=1;i<=len;i++){
            int c = s[i] - 'a';
            u = tr[u][c];
            for(int j = u; j; j = pre[j]){
                if(e[j]){
                    pos[p[j]].push_back(i);
                }
            }
        }
    }
}
int main() {
    scanf("%s",s+1);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%s",&k[i], t+1);
        len[i] = strlen(t+1);
        AC::insert(t, i);
    }
    AC::build();
    AC::get();
    for(int i=1;i<=n;i++){
        if(pos[i].size() < k[i]){
            puts("-1");continue;
        }
        int res = inf;
        for(int j = k[i] - 1; j < pos[i].size(); j++){
            res = min(res, pos[i][j] - pos[i][j - k[i] + 1] + len[i]);
        }
        printf("%d
", res);
    }
    return 0;
}

10. Resource Archiver

HDU - 3247

很疑惑为什么网上有那么多错误题解?

下面这份代码建立在资源串不存在某个是某个子串的情况,再建好的Trie图中,从每个资源串末尾进行BFS,求出它到其他资源串末尾结点的最短距离(不能经过病毒串末尾结点),当然还要处理空串到其他所有资源串的距离。

然后跑一个TSP就可以了

const int N = 100000 + 5;
int n, m;
char s[N];
namespace AC{
    int tr[N][2],tot;
    int e[N],fail[N], vir[N], pos[20];
    int dis[20][20], d[N], dp[1<<10][20];//dp[i][j]表示资源串状态为 i(二进制),走到了 j 号资源串的末尾结点
    queue<int> q;
    void init(){
        memset(dis, -1, sizeof dis);
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = vir[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s, int id, int type){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i] - '0']){
                tr[u][s[i] - '0'] = ++tot;
            }
            u = tr[u][s[i] - '0'];
        }
        if(type == -1){
            vir[u] = 1;//标记病毒
        }else{
            pos[id] = u;    
        }
    }
    void build(){
        for(int i=0;i<2;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            vir[u] |= vir[fail[u]];//fail树上进行传递
            for(int i=0;i<2;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void bfs(int s){
        queue<int> q;
        memset(d, -1, sizeof d);//-1表示不能访问,这里要和dis的默认一致,都为-1
        d[pos[s]] = 0; // 时刻注意pos[s]表示Trie图中s号资源串的结点编号
        q.push(pos[s]);
        
        while(q.size()){
            int x = q.front();q.pop();
            for(int i=0;i<2;i++){
                int u = tr[x][i];
                if(!vir[u] && d[u] == -1){
                    d[u] = d[x] + 1;
                    q.push(u);
                }
            }
        }
        for(int i=1;i<=n;i++){
            dis[s][i] = d[pos[i]];
        }
    }
    void solve(){
        memset(dp, 0x3f, sizeof dp);
        dp[0][0] = 0;
        for(int i=0; i<(1<<n); i++){
            for(int j=0; j<=n; j++){ //j要从0开始,
                for(int k = 1; k <= n; k++){ //k从1开始就行,0代表空串
                    if(dis[j][k] == -1)continue;
                    if(i >> (k-1) & 1) continue;
                    int &res = dp[i|(1<<(k-1))][k];
                    res = min(res, dp[i][j] + dis[j][k]);
                }
            }
        }
        int res = inf;
        for(int i=1;i<=n;i++)
            res = min(res, dp[(1<<n)-1][i]);
        printf("%d
", res);
    }
}
int main() {
    while(~scanf("%d%d",&n,&m)){
        if(n == 0 && m == 0){
            break;
        }
        AC::init();
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            AC::insert(s, i, i);
        }
        for(int i=1;i<=m;i++){
            scanf("%s",s+1);
            AC::insert(s, -1, -1);
        }
        AC::build();
        AC::bfs(0);
        for(int i=1;i<=n;i++) AC::bfs(i);
        AC::solve();
    }
    return 0;
}

11. 小明系列故事——女友的考验

HDU - 4511

自动机上面DP,和前面的题目基本一样

const int N = 500 + 5;
const int M = 55;
int n, m, k;
int a[N];
double x[M], y[M];
double d[55][55];
namespace AC{
    int tr[N][55],tot;
    int e[N],fail[N];
    int vis[N][55];
    double res[N][55];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            memset(vis, 0, sizeof vis);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(int *s, int len){
        int u = 0;
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i]]){
                tr[u][s[i]] = ++tot;
            }
            u = tr[u][s[i]];
        }
        e[u] ++;
    }
    void build(){
        for(int i=1;i<=n;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] |= e[fail[u]];
            for(int i=1;i<=n;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    double get(int u, int pos){
        if(e[u]) return 1e15;
        if(pos == n) return 0;
        if(vis[u][pos]) return res[u][pos];
        vis[u][pos] = 1;
        double &ans = res[u][pos];
        ans = 1e15;
        for(int i=pos+1;i<=n;i++){
            ans = min(ans, d[pos][i] + get(tr[u][i], i));
        }
        return ans;
    }
}
double S(double x){ return x * x;}
int main() {
    while(~scanf("%d%d",&n,&m)){
        if(n == 0 && m == 0) break;
        AC::init();
        for(int i=1;i<=n;i++){
            scanf("%lf%lf",&x[i], &y[i]);//一开始就要输入double,因为下面计算距离时,有可能爆LL
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                d[i][j] = sqrt(S(x[i]-x[j]) + S(y[i]-y[j]));
            }
        }
        for(int i=1;i<=m;i++){
            scanf("%d",&k);
            for(int j=1;j<=k;j++)scanf("%d",&a[j]);
            AC::insert(a, k);
        }
        AC::build();
        double res =  AC::get(AC::tr[0][1], 1);
        if(res == 1e15)puts("Can not be reached!");
        else 
            printf("%.2f
", res);
    }
    return 0;
}

12. Wireless Password

HDU - 2825

题意:

给出(m)个长度小于等于(10)的字符串,然后求长度为(n(nle 25))的字符串,要求其最少包含(m)个字符串中的(k)个(可以有覆盖部分),问这样的字符串有多少个。

分析:

d[i][j][k] 表示长度为 i,在 j 结点,包含字符串状态为 k 的种类数

考虑 AC自动机上 j 结点的转移,u = tr[j][c], e[u]表示u结点的匹配集合,则有:

d[i+1][u][k|e[u]] += d[i][j][k]

const int N = 100 + 5;
const int mod = 20090717;
int n, m, k;
char s[N];
int d[26][N][1<<10], cnt[1<<10];
namespace AC{
    int tr[N][26],tot;
    int e[N],fail[N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s, int id){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i] - 'a']){
                tr[u][s[i] - 'a'] = ++tot;
            }
            u = tr[u][s[i] - 'a'];
        }
        e[u] |= 1 << (id-1);
    }
    void build(){
        for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] |= e[fail[u]];
            for(int i=0;i<26;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void solve(){
        memset(d, 0, sizeof d);
        d[0][0][0] = 1;
        //25 * 100 * 1024 * 26
        for(int i=0;i<n;i++){
            for(int j=0;j<=tot;j++){
                for(int k=0;k<(1<<m);k++){
                    //很强大的优化
                    if(d[i][j][k] == 0) continue;
                    for(int c = 0; c < 26; c++){
                        int u = tr[j][c];
                        int &res = d[i+1][u][k|e[u]];
                        res = (res + d[i][j][k]) % mod;
                    }
                }
            }
        }
        int res = 0;
        for(int i=0;i<(1<<m);i++){
            if(cnt[i] < k) continue;
            for(int j=0;j<=tot;j++){
                res = (res + d[n][j][i]) % mod;
            }
        }
        printf("%d
", res);
    }
}

int main() {
    for(int i=0;i<(1<<10);i++){
        for(int j=0;j<10;j++){
            if(i >> j & 1) cnt[i]++;
        }
    }
    while(~scanf("%d%d%d",&n,&m,&k)){
        if(n == 0 && m == 0 && k == 0) break;
        AC::init();
        for(int i=1;i<=m;i++){
            scanf("%s",s+1);
            AC::insert(s, i);
        }
        AC::build();
        AC::solve();
    }
    return 0;
}

13.Lost‘s revenge

HDU - 3341

题意:

DNA序列,仅包含"ACGT"四种字符,给 (N(Nle 50)) 个串,每个串长度不超过10,最后给出一个字符串S((|S| le 40)), 求将 S 重新排列之后,最多能够包含N个串中的多少个?(允许覆盖)

分析:

重组串长度不超过40,字符种类数为4,如果用d[i][j][k][l][m]来表示匹配到 i 号结点时,有 j 个 ‘A‘, k 个 ‘C‘, l 个 ‘G‘, m 个 ‘T‘时,最多包含几个串

枚举 i 的转移,u=tr[i][c], 若 c 表示 ‘A‘, 则 d[u][j+1][k][l][m] = max(d[u][j+1][k][l][m], d[i][j][k][l][m] + e[u])

const int N = 500 + 5;
int n;
char s[N];
int f[4];
inline int xid(char ch) {
    if(ch == 'A') return 0;
    if(ch == 'C') return 1;
    if(ch == 'G') return 2;
    if(ch == 'T') return 3;
}
namespace AC{
    int tr[N][4],tot;
    int e[N],fail[N];
    queue<int> q;
    vector<vector<vector<vector<int>>>> d[N];
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            int c = xid(s[i]);
            if(!tr[u][c]){
                tr[u][c] = ++tot;
            }
            u = tr[u][c];
        }
        e[u]++;
    }
    void build(){
        for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] += e[fail[u]];
            for(int i=0;i<4;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void solve(){
        memset(f, 0, sizeof f);
        int len = strlen(s+1);
        for(int i=1;i<=len;i++){
            f[xid(s[i])] ++;
        }

        for(int i = 0; i <= tot; i++){
            d[i].clear();
            d[i].resize(f[0]+1);
            for(int j = 0; j <= f[0]; j++){
                d[i][j].resize(f[1] + 1);
                for(int k = 0; k <= f[1]; k++){
                    d[i][j][k].resize(f[2]+1);
                    for(int l = 0; l <= f[2]; l++){
                        d[i][j][k][l].resize(f[3]+1, -1);
                    }
                }
            }
        }
        d[0][0][0][0][0] = 0;
        int res = 0;
        for(int num = 0; num <= len; num ++){
            for(int i = 0; i <= tot; i++){
                for(int j = 0; j <= f[0] && j <= num; j++){
                    for(int k = 0; k <= f[1] && j + k <= num; k++){
                        for(int l = 0; l <= f[2] && j + k + l <= num; l++){
                            int m = num - j - k - l;
                            if(m > f[3] || d[i][j][k][l][m] == -1) continue;

                            if(j < f[0]) d[tr[i][0]][j+1][k][l][m] = max(d[tr[i][0]][j+1][k][l][m], d[i][j][k][l][m] + e[tr[i][0]]);
                            if(k < f[1]) d[tr[i][1]][j][k+1][l][m] = max(d[tr[i][1]][j][k+1][l][m], d[i][j][k][l][m] + e[tr[i][1]]);
                            if(l < f[2]) d[tr[i][2]][j][k][l+1][m] = max(d[tr[i][2]][j][k][l+1][m], d[i][j][k][l][m] + e[tr[i][2]]);
                            if(m < f[3]) d[tr[i][3]][j][k][l][m+1] = max(d[tr[i][3]][j][k][l][m+1], d[i][j][k][l][m] + e[tr[i][3]]);
                            
                            if(num == len);
                            res = max(res, d[i][j][k][l][m]);
                            
                        }
                    }
                }
            }
        }
        printf("%d
", res);
    }
}


int main() {
    int cas = 0;
    while(~scanf("%d",&n)){
        if(n == 0) break;
        AC::init();
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            AC::insert(s);
        }
        AC::build();
        scanf("%s", s+1);
        printf("Case %d: ", ++cas);
        AC::solve();
    }
    return 0;
}

14 DNA Sequence

POJ - 2778

参考题解:https://blog.csdn.net/morgan_xww/article/details/7834801

const int N = 100 + 5;
const int mod = 100000;
int n, m;
char s[N]; 
struct mat{
    int r, c;
    ll s[N][N];
    mat(int r=0,int c=0):r(r),c(c){
        memset(s, 0, sizeof s);
    }
};
mat operator*(const mat&a, const mat&b){
    mat c = mat(a.r, b.c);
    for (int i = 0; i < c.r;i++){
        for (int j = 0; j < c.c;j++)
            for (int k = 0; k < a.c;k++)
                c.s[i][j] = (c.s[i][j] + a.s[i][k] * b.s[k][j]) % mod;
    }
    return c;
}
mat power(mat a,int b){
    mat res = a;
    b--;
    for (; b;b>>=1){
        if(b & 1)
            res = res * a;
        a = a * a;
    }
    return res;
}
inline int xid(char ch){
    if(ch == 'A') return 0;
    if(ch == 'C') return 1;
    if(ch == 'G') return 2;
    if(ch == 'T') return 3;
}
namespace AC{
    int tr[N][4],tot;
    int e[N],fail[N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            int c = xid(s[i]);
            if(!tr[u][c]){
                tr[u][c] = ++tot;
            }
            u = tr[u][c];
        }
        e[u] ++;
    }
    void build(){
        for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] += e[fail[u]];
            for(int i=0;i<4;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void solve(){
        mat t(tot+1, tot+1);
        for(int i=0;i<=tot;i++){
            if(e[i]) continue;
            for(int j=0;j<4;j++){
                int u = tr[i][j];
                if(e[u]) continue;
                t.s[i][u] ++;
            }
        }
        t = power(t, n);
        int res = 0;
        for(int i=0;i<=tot;i++) res = (res + t.s[0][i]) % mod;
        printf("%d
", res);
    }
}
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++){
        scanf("%s", s+1);
        AC::insert(s);
    }
    AC::build();
    AC::solve();
    return 0;
}

15. 考研路茫茫——单词情结

HDU - 2243

和上个题基本一样

const int N = 30 + 5;
int m;
ll n;
char s[N]; 
struct mat{
   int r, c;
   ull s[N][N];
   mat(int r=0,int c=0):r(r),c(c){
       memset(s, 0, sizeof s);
   }
   void print(){
       for(int i=0;i<r;i++){
           for(int j=0;j<c;j++){
               printf("%llu ", s[i][j]);
           }
           puts("");
       }
   }
}one;
mat operator*(const mat&a, const mat&b){
   mat c = mat(a.r, b.c);
   for (int i = 0; i < c.r;i++){
       for (int j = 0; j < c.c;j++)
           for (int k = 0; k < a.c;k++)
               c.s[i][j] = c.s[i][j] + a.s[i][k] * b.s[k][j];
   }
   return c;
}
mat operator+(const mat&a, const mat&b){
   mat c = mat(a.r, b.c);
   for (int i = 0; i < c.r;i++){
       for (int j = 0; j < c.c;j++){
           c.s[i][j] = a.s[i][j] + b.s[i][j];
       }
   }
   return c;
}
mat power(mat a,ll b){
   mat res = a;
   b--;
   for (; b;b>>=1){
       if(b & 1)
           res = res * a;
       a = a * a;
   }
   return res;
}
mat getsum2(mat a, ll b){
   if(b == 0) return one;
   if(b == 1) return a;
   if(b & 1){
       return (one + power(a, (b+1)/2)) * getsum2(a, b / 2) + power(a, (b+1)/2);
   }
   else{
       return (one + power(a, b/2)) * getsum2(a, b / 2);
   }
}
ull ksm(ull a, ull b){
   ull res = 1;
   for(;b;b>>=1){
       if(b & 1) res = res * a;
       a = a * a;
   }
   return res;
}
ull getsum(ull a, ull b){
   if(b == 0) return 1;
   if(b == 1) return a;
   if(b & 1){
       return (ksm(a, (b+1)/2) + 1) * getsum(a, b / 2) + ksm(a, (b+1)/2);
   }
   else{
       return (ksm(a, b/2) + 1) * getsum(a, b / 2);
   }
}
namespace AC{
   int tr[N][26],tot;
   int e[N],fail[N];
   queue<int> q;
   void init(){
       for(int i=0;i<=tot;i++){
           memset(tr[i],0,sizeof tr[i]);
           fail[i] = e[i] = 0;
       }
       tot = 0;
   }
   void insert(char *s){
       int u = 0;
       int len = strlen(s + 1);
       for(int i=1;i<=len;i++){
           int c = s[i] - 'a';
           if(!tr[u][c]){
               tr[u][c] = ++tot;
           }
           u = tr[u][c];
       }
       e[u] ++;
   }
   void build(){
       for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
       while(q.size()){
           int u = q.front();q.pop();
           e[u] += e[fail[u]];
           for(int i=0;i<26;i++){
               if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
               else tr[u][i] = tr[fail[u]][i];
           }
       }
   }
   void solve(){
       mat t(tot+1, tot+1);
       one = mat(tot+1, tot+1);
       for(int i=0;i<=tot;i++)one.s[i][i] = 1;
       for(int i=0;i<=tot;i++){
           if(e[i]) continue;
           for(int j=0;j<26;j++){
               int u = tr[i][j];
               if(e[u]) continue;
               t.s[i][u] ++;
           }
       }
       t = getsum2(t, n);
       ull res = 0;
       for(int i=0;i<=tot;i++) res += t.s[0][i];
       printf("%llu
", getsum(26, n) - res);
   }
}
int main(){
   while(~scanf("%d%lld",&m,&n)){
       AC::init();
       for(int i=1;i<=m;i++){
           scanf("%s", s+1);
           AC::insert(s);
       }
       AC::build();
       AC::solve();
   }
   return 0;
}

16. Censored!

POJ - 1625

d[i][j] 表示长度为 i,到达 j 号节点的状态

if(e[u] == 0){
    d[i+1][u] += d[i][j];// u = tr[j][c];
}
  1. 输入的字符不太正常,应该是会超过127导致出现负数
  2. 没取模,应该用大数
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "33[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "33[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 100 + 5;
const int base = 10;
int n, m, p;
int xid[300];
char s[N], a[N];
int get(char ch){
    for(int i=1;i<=m;i++){
        if(ch == a[i]) return i-1;
    }
}
struct BigInt
{
    int v[105], len;
    BigInt(int r = 0)
    {
        memset(v, 0, sizeof(v));
        for(len = 0; r > 0; r /= base) v[len++] = r % base;
    }
    BigInt operator + (const BigInt &a)
    {
        BigInt ans;
        int i , c = 0;
        for(i = 0; i < len || i < a.len || c > 0; i++)
        {
            if(i < len)c += v[i];
            if(i < a.len)c += a.v[i];
            ans.v[i] = c % base;
            c /= base;
        }
        ans.len = i;
        return ans;
    }
    void print()
    {
        printf("%d", len == 0 ? 0 : v[len - 1]);
        for(int i = len - 2; i >= 0; i--)
            printf("%d", v[i]);
        printf("
");
    }
};
namespace AC{
    int tr[N][55],tot;
    int e[N],fail[N];
    BigInt d[55][N];
    int vis[55][N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            int c = get(s[i]);
            if(!tr[u][c]){
                tr[u][c] = ++tot;
            }
            u = tr[u][c];
        }
        e[u] ++;
    }
    void build(){
        for(int i=0;i<m;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] += e[fail[u]];
            for(int i=0;i<m;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void solve(){
        d[0][0] = BigInt(1);
        for(int i = 0;i < n; i++){
            for(int u = 0; u <= tot; u++){
                if(e[u]) continue;
                for(int c = 0; c < m; c++){
                    int v = tr[u][c];
                    if(!e[v]){
                        d[i+1][v] = d[i+1][v] + d[i][u];
                    }
                }
            }
        }
        BigInt res;
        for(int i=0;i<=tot;i++){
            res =  res + d[n][i];
        }
        res.print();
    }
}
int main(){
    scanf("%d%d%d",&m,&n,&p);
    scanf("%s", a+1);
    for(int i=1;i<=p;i++){
        scanf("%s", s+1);
        AC::insert(s);
    }
    AC::build();
    AC::solve();
    return 0;
}

17. Walk Through Squares

HDU - 4758

d[u][i][j][k] 表示走到 (u) 节点,走了$ i$ 次 ‘D‘, (j) 次 ‘R‘, 经过的模板串状态为 (k)时的方案数

const int N = 200 + 5;
const int mod = 1000000007;
int n, m;
char s[N];
inline xid(char ch){
    if(ch == 'R') return 1;
    else return 0;
}
namespace AC{
    int tr[N][2],tot;
    int e[N],fail[N];
    int d[N][105][105][4];
    bool vis[N][105][105][4];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s, int id){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            int c = xid(s[i]);
            if(!tr[u][c]){
                tr[u][c] = ++tot;
            }
            u = tr[u][c];
        }
        e[u] |= 1 << id;
    }
    void build(){
        for(int i=0;i<2;i++)if(tr[0][i])q.push(tr[0][i]);
        while(q.size()){
            int u = q.front();q.pop();
            e[u] |= e[fail[u]];
            for(int i=0;i<2;i++){
                if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
                else tr[u][i] = tr[fail[u]][i];
            }
        }
    }
    void solve(){
        memset(d, 0, sizeof d);
        memset(vis, 0, sizeof vis);
        d[0][0][0][0] = 1;
        vis[0][0][0][0] = 1;
        for(int i=0; i <= n; i++){
            for(int j=0; j<= m; j++){
                for(int u = 0; u<= tot; u++){
                    for(int k = 0; k < 4; k++){
                        if(vis[u][i][j][k] == 0) continue; // 如果该点压根就到不了,就不要参与转移
                        if(i < n){
                            int v = tr[u][0];
                            vis[v][i + 1][j][k | e[v]] = 1;
                            (d[v][i + 1][j][k | e[v]] += d[u][i][j][k]) %= mod;
                        }
                        if(j < m){
                            int v = tr[u][1];
                            vis[v][i][j + 1][k | e[v]] = 1;
                            (d[v][i][j + 1][k | e[v]] += d[u][i][j][k]) %= mod;
                        }
                    }
                }
            }
        }
        ll res = 0;
        for(int i=0; i<=tot; i++){
            (res += d[i][n][m][3]) %= mod;
        }
        printf("%lld
", res);
    }
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d", &m, &n);
        AC::init();
        scanf("%s", s+1);
        AC::insert(s, 0);
        scanf("%s", s+1);
        AC::insert(s, 1);
        AC::build();
        AC::solve();
    }
    return 0;
}

以上是关于AC自动机的主要内容,如果未能解决你的问题,请参考以下文章

POJ3691DNA repair(AC自动机,DP)

HDU4057 Rescue the Rabbit(AC自动机+状压DP)

Codeforces 86C Genetic engineering(AC自动机+DP)

POJ1699 Best Sequence(AC自动机+状压DP)

POJ - 2778 ~ HDU - 2243 AC自动机+矩阵快速幂

HDU2457 DNA repair(AC自动机+DP)