Searching the String ZOJ - 3228 (AC自动机+思维)

题目链接

题目大意:给定一个文本串,n个模式串,其中模式串有两种,一种是同一模式串可以有重合的部分,如ababa 出现两次aba,另一种是相同的模式串不允许重合,如ababa 出现1次aba.问所有的模式串各自在文本串中出现了多少次.

思路:对于第一种,可以重合的模式串,就是朴素的ac自动机。
对于第二种,相同模式串不能重合,我们可以知道,相同模式串有重合当且仅当第二次出现的位置-第一次出现的位置<字符串长度,
我们可以开一个数组last[],last[i]记录以i号节点结尾的单词,最新匹配成功的位置 如在ababa中 ab第一次匹配的位置是 index=1 第二次 index=3 ,分别是2个b的下标,每次匹配到这个单词,判断一下 当前位置now-len>=last[i] 是的话就可以ans+1 .匹配过程基本上还是ac自动机中query函数的流程

void query(char *s)
{
    int len=strlen(s),root=0;
    for(int i=0;i<len;i++)
    {
        int u=tree[root][s[i]-'a'];
        root=u;
        while(u)
        {
        //ans[u][0]代表允许重合的情况,ans[u][1]代表不允许重回的情况,都算出来更简洁
            if(isstr[u]) ans[u][0]++;
            //isstr[u]: u结尾的路径是模式串的话,存单词长度,否则是0
            if(isstr[u] && i-last[u]>=isstr[u] )
            {
                ans[u][1]++;
                last[u]=i;//更新u结尾的模式串,在文本串中最新出现的位置
            }
            u=fail[u];
        }
    }
}

具体细节见代码注释

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define INF 1000000
#define LL unsigned long long
const int maxn=6e5+100;
int tot;//结点数 从1开始
int isstr[maxn];//结尾的长度
int tree[maxn][26];
int fail[maxn];
char s1[maxn];//文本串
int kinds[maxn/6];//第i个模式串的种类 可重合/不可
int ans[maxn][2];//节点最终的答案
int num[maxn/6];//每个单词的尾节点编号
int last[maxn];//以其结尾的单词上次匹配成功的位置
void Init()
{
    memset(isstr,0,sizeof(isstr));
    memset(fail,0,sizeof(fail));
    memset(tree,0,sizeof(tree));
    tot=0;
    memset(ans,0,sizeof(ans));
    memset(num,0,sizeof(num));
    fill(last,last+maxn,-1);//初始化成-1  初始化成0的话abab里面第一处ab就漏记了 
}
int Insert(char* s)
{
    int len=strlen(s);
    int root=0;
    for(register int i=0;i<len;i++)
    {
        int id=s[i]-'a';
        if(!tree[root][id]) tree[root][id]=++tot;
        root=tree[root][id];
    }
    isstr[root]=len;
    return root;//返回节点编号
}
void getfail()
{
    queue<int> q;
    //初始化第二层,fail指针全指向根节点,入队
    for(int i=0;i<26;i++)
    {
        if(tree[0][i])
        {
            q.push(tree[0][i]);
        }
    }
    while(!q.empty())
    {
        int v=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            //如果这个孩子存在,它的fail指向的就是父亲的fail所指向节点的孩子
            if(tree[v][i]) 
            {
                fail[tree[v][i]]=tree[fail[v]][i];
                q.push(tree[v][i]);
            }
            //否则,让这个孩子指向父亲的fail所指向节点的孩子,便于查询
            else tree[v][i]=tree[fail[v]][i];
        }
    }
}
void query(char *s)
{
    int len=strlen(s),root=0;
    for(int i=0;i<len;i++)
    {
        int u=tree[root][s[i]-'a'];
        root=u;
        while(u)
        {
        //ans[u][0]代表允许重合的情况,ans[u][1]代表不允许重回的情况,都算出来更简洁
            if(isstr[u]) ans[u][0]++;
            //isstr[u]: u结尾的路径是模式串的话,存单词长度,否则是0
            if(isstr[u] && i-last[u]>=isstr[u] )
            {
                ans[u][1]++;
                last[u]=i;//更新u结尾的模式串,在文本串中最新出现的位置
            }
            u=fail[u];
        }
    }
}
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    int cnt=1;
    while(~scanf("%s",s1))
    {
        Init();
        int n;
        scanf("%d",&n);
        char str[10];
        for(int i=1;i<=n;i++)
        {
            scanf("%d%s",kinds+i,str);
            num[i]=Insert(str);
        }
        getfail();
        query(s1);
        printf("Case %d\n",cnt++);
        for(int i=1;i<=n;i++) printf("%d\n",ans[num[i]][kinds[i]]);
        putchar('\n');
    }
    system("pause");
    return 0;
}

你可能感兴趣的:(AC自动机,字符串,算法)