POJ 4052 Hrinity (金华邀请赛I题) AC自动机

题目大意:

就是给你 n 个压缩形式的字符串 n <= 2500, 然后每个字符串解压前和解压后的长度都不超过1100, 然后给出一个长串,也是压缩格式,输入保证其长度在压缩和解压之后长度都不超过5100000,现在对于 t 组测试数据,每组输出给出的n个字符串在当组最后给的长串中出现了几次,其中如果串A, B都出现了,且 A是B的子串,则不计A


大致思路:

明显的AC自动机的题......但是模拟做的时候还是TLE了很多遍,后来发现一个地方的回溯可以不用回溯...才终于过掉了


首先对于每组数据的n个串建立一颗Trie树,算出fail指针(AC自动机的转移),并且用end[num]数组记录以结点num作为结尾的串的编号是多少。

然后遍历文本串,用flag数组记录n个模板串种有哪些串是出现过的,然后对于出现过的串在AC自动机上进行转移找出其子串,见子串的flag标记去掉,最后统计标记有flag的串的个数即可。


代码如下:

Result  :  Accepted     Memory  :  28620 KB     Time  :  360 ms

/*
 * Author: Gatevin
 * Created Time:  2014/11/15 13:13:23
 * File Name: AC.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

int n;
char ts[5100005];
char all[5100005];
char s[2505][1105];
int next[5100005][26], fail[5100005];
int end[5100005];
bool flag[2505];
int L, root, ans;

int newnode()//新节点
{
    for(int i = 0; i < 26; i++)
       next[L][i] = -1;
    end[L++] = 0;
    return L - 1;
}

void init()//初始化Trie树
{
    L = 0;
    root = newnode();
    memset(flag, 0, sizeof(flag));
    return;
}

void insert(char* t, int id)//插入模板串
{
    int now = root;
    for(; *t; t++)
    {
        if(next[now][*t - 'A'] == -1)
            next[now][*t - 'A'] = newnode();
        now = next[now][*t - 'A'];
    }
    end[now] = id;//记录模板串编号
    return;
}

void build()//计算fail数组
{
    queue <int> Q;
    fail[root] = root;
    Q.push(root);
    while(!Q.empty())
    {
        int now = Q.front();
        Q.pop();
        for(int i = 0; i < 26; i++)
            if(next[now][i] == -1)
                next[now][i] = now == root ? root : next[fail[now]][i];
            else
            {
                fail[next[now][i]] = now == root ? root : next[fail[now]][i];
                Q.push(next[now][i]);
            }
    }
    return;
}

void sea(int id)//对于出现了的模板串,寻找其子串
{
    int now = root;
    char *t = &s[id][0];
    for(; *t; t++)
    {
        now = next[now][*t - 'A'];
        int tmp = now;
        if(*(t + 1) == '\0')
        {
            tmp = fail[tmp];
        }
        while(tmp != root)
        {
            if(end[tmp] && flag[end[tmp]])
            {
                flag[end[tmp]] = 0;
            }
            tmp = fail[tmp];
        }
    }
    return;
}

int query(char* t)//查找出现的模板串
{
    int now = root;
    ans = 0;
    for(; *t; t++)
    {
        now = next[now][*t - 'A'];
        //int tmp = now;
        if(end[now])//这里不用while(tmp != root)标记所有子串,减少计算量
        {
            flag[end[now]] = 1;
        }
    }
    for(int i = 1; i <= n; i++)
    {
        if(flag[i])
        {
            sea(i);
        }
    }
    for(int i = 1; i <= n; i++)
    {
        if(flag[i]) ans++;
    }
    return ans;
}

void dcom(char * s)//解压还原字符串
{
    char* t = &ts[0];
    int now = 0;
    for(; *t; t++)
    {
        if(*t == '[')
        {
            t++;
            int tmp = 0;
            while(*t <= '9' && *t >= '0')
            {
                tmp = tmp*10 + *t - '0';
                t++;
            }
            while(tmp--)
            {
                s[now++] = *t;
            }
            t++;
        }
        else
        {
            s[now++] = *t;
        }
    }
    s[now] = '\0';
    return;
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        init();
        for(int i = 1; i <= n; i++)
        {
            scanf("%s", ts);
            dcom(s[i]);
            insert(s[i], i);
        }
        build();
        scanf("%s", ts);
        dcom(all);
        printf("%d\n", query(all));
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值