AC自动机 zoj3228 Searching the String

传送门:点击打开链接

题意:告诉你原串,告诉你很多个子串,有两种匹配模式,一种是可以重叠的匹配,一种是不能重叠的,问每个子串出现的次数。子串可能有重复的出现。

思路:这题揭示了AC自动机的两种处理方法。

首先是当AC自动机中出现有串重复的时候,我们应该怎么处理,我这里用到了一个数组pre,如果这个节点结尾的子串是第一次出现,pre标记为自己,如果这个节点结尾的子串不是第一次出现,那么就把pre标记成第一次出现的串。最后输出答案的时候,输出的是ans[pre[i]]],这里其实就是有许多个相同子串,一个子串我们只要算一次就行了,然后把相同子串的答案和我们计算的那个相等就行。

其次是遇到不能重叠的子串匹配,我们应当如何去处理。我们维护了一个数组last来表示某个子串上次匹配的位置,看这个位置加上子串的长度,是否小于等于当前匹配的位置,如果小于等于,则说明不会和上次重叠,那么这次找到了就可以更新答案。

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define fuck(x) cout<<"["<<x<<"]"
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w+",stdout)
using namespace std;
typedef long long LL;

/*MX为总长度*/
const int MN = 1e5 + 5;
const int MX = 6e5 + 5;
const int P = 26;

int last[MN], ans[MN], pre[MN];

struct AC_machine {
    int rear, root, m, len[MX];
    int Next[MX][P], Fail[MX], End[MX];

    void Init(int _m) {
        m = _m;
        rear = 0;
        root = New();
    }

    int New() {
        End[rear] = 0;
        for(int i = 0; i < P; i++) {
            Next[rear][i] = -1;
        }
        return rear++;
    }

    void Add(const char *A, int u) {
        int n = strlen(A), now = root;
        for(int i = 0; i < n; i++) {
            int id = A[i] - 'a';
            if(Next[now][id] == -1) {
                Next[now][id] = New();
            }
            now = Next[now][id];
        }
        if(End[now]) pre[u] = End[now];
        else pre[u] = End[now] = u;

        last[u] = -1;
        len[now] = n;
    }

    void Build() {
        queue<int>Q;
        Fail[root] = root;
        for(int i = 0; i < P; i++) {
            if(Next[root][i] == -1) {
                Next[root][i] = root;
            } else {
                Fail[Next[root][i]] = root;
                Q.push(Next[root][i]);
            }
        }

        while(!Q.empty()) {
            int u = Q.front();
            Q.pop();

            for(int i = 0; i < P; i++) {
                if(Next[u][i] == -1) {
                    Next[u][i] = Next[Fail[u]][i];
                } else {
                    Fail[Next[u][i]] = Next[Fail[u]][i];
                    Q.push(Next[u][i]);
                }
            }
        }
    }

    void Query_1(char *S) {
        int n = strlen(S), now = root, ret = 0;

        for(int i = 0; i < n; i++) {
            now = Next[now][S[i] - 'a'];
            int temp = now;

            while(temp != root) {
                if(End[temp]) ans[End[temp]]++;
                temp = Fail[temp];
            }
        }
    }

    void Query_2(char *S) {
        int n = strlen(S), now = root, ret = 0;

        for(int i = 0; i < n; i++) {
            now = Next[now][S[i] - 'a'];
            int temp = now;

            while(temp != root) {
                if(End[temp] && (last[End[temp]] == -1 || last[End[temp]] + len[temp] <= i)){
                    ans[End[temp]]++;
                    last[End[temp]] = i;
                }
                temp = Fail[temp];
            }
        }
    }
} AC;

char word[10], line[MN];
vector<pair<string, int> >A;

int main() {
    int m, ansk = 0; //FIN;
    while(~scanf("%s", line)) {
        memset(ans, 0, sizeof(ans));
        A.clear();
        scanf("%d", &m);
        AC.Init(m);

        int r1 = 0, r2 = 0;
        for(int i = 1; i <= m; i++) {
            int type;
            scanf("%d%s", &type, word);
            if(type == 0) {
                AC.Add(word, i);
            } else A.push_back(make_pair(string(word), i));
        }
        AC.Build();
        AC.Query_1(line);

        AC.Init(A.size());
        for(int i = 0; i < A.size(); i++) {
            AC.Add(A[i].first.c_str(), A[i].second);
        }

        AC.Build();
        AC.Query_2(line);

        printf("Case %d\n", ++ansk);
        for(int i = 1; i <= m; i++) printf("%d\n", ans[pre[i]]);
        printf("\n");
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值