hdu 2896 病毒侵袭【AC自动机模板题】

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2896

在杭电的oj,但凡有可能超内存,都要首选 c++编译器的交题,G++的编译器用的内存要比C++的大很多;

这是一道AC自动机的模板题,我这里就只是讲一些细节就好了;

标准的模板,在用Trie存字符串的时候,在字符串末尾的位置会做一下标记,一般是int变量初始值设置为0,末尾位置设置为 1,但是,使用fail(失配指针)的时候,不是判断当前的这个用来标记末尾变量值是否是1才去使用,而是判断 大于等于0,也就是说,即使当前这个字符不是字符串的末尾字符也要去访问fail指针,想想为什么?我们使用fail的真正目的,就是去判断我们正在匹配的字符串,是否存在一个以当前访问的字符为尾的后缀字符串是我们想要匹配的其中一个子字符串;打个比方; abcdef    bcd,如果主字符串是abcdefgh,我们先用第一个子字符与主字符串匹配,当我们访问到第一个子字符串的 d 这个位置的时候,显然这个位置并不是这个字符串的末尾,但是我们仍然要去访问 fail指针,而d 的fail指针是指向 第二个字符串的d这个位置的(如果不是很明白为什么fail指向的是第二个子字符串的d,建议先去复习一下AC自动机的算法),fail指向的这个d又正好是第二个字符串的末尾位置,如果我们不是根据大于等于0来使用fail的话,就有可能会漏掉第二个字符能和主字符串匹配的可能;

上面说的也是这道题的一个细节;我们在建Trie的时候,在子字符末尾的位置的int变量用来存病毒的编号,并且初始化为 0,当使用fail的时候,即使当前编号值为0,也要去使用fail指针,这是一个细节,还有一个就是,模板普遍是用过这个int变量就会把他设置为-1,但是这题并不能这样做,因为要比较的主字符串不止一个,所以一定要保证Trie在程序结束前不能被破坏;这里可以用一个vis数组来标记,每匹配完一个主字符串就清空一下vis; 

这道题用到了set来存最终的编号,既可以自动排序,也可以自动剔除重复,挺不错的; (代码挺乱的,也没有什么时间去改,还是看看其他博客的代码吧,这里看看上面的细节就行了,注意交题的时候用C++!)

#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<queue>

using namespace std;

#define Maxn 10005

struct Trie {
    int num;
    Trie *child[130];
    Trie *fail;
    Trie () {
        memset(child,0,sizeof(child));
        num = 0;
        fail = NULL;
    }
} *root;

set<int> ans; int vis[502];

void insert_ (char *str, int n) {
    Trie *rt = root;
    int len = strlen(str),index;
    for (int i = 0; i < len; ++i) {
        index = str[i];
        if(!rt->child[index]) {
            rt->child[index] = new Trie ();
        }
        rt = rt->child[index];
    }
    rt->num = n;
}

void getfail () {
    Trie *p,*tmp;
    queue<Trie *> qu;
    qu.push(root);

    while (!qu.empty()) {
        tmp = qu.front(); qu.pop();
        for (int i = 0; i < 130; ++i) {
            if(tmp->child[i]) {
                if(tmp == root) tmp->child[i]->fail = root;
                else {
                    p = tmp->fail;
                    while (p) {
                        if(p->child[i]) {
                            tmp->child[i]->fail = p->child[i];
                            break;
                        }
                        else p = p->fail;
                    }
                    if(!p) tmp->child[i]->fail = root;
                }
                qu.push(tmp->child[i]);
            }
        }
    }
}

void query (char *str) {
    int len = strlen(str),index;
    Trie *rt = root,*p;

    for (int i = 0; i < len; ++i) {
        index = str[i];
        while (!rt->child[index] && rt != root) rt = rt->fail;
        rt = rt->child[index];
        rt = (rt == NULL) ? root : rt;
        p = rt;
        while (p != root && p->num >= 0 && !vis[p->num]) {  // 即使编号为0,也要使用fail,看看有没有其他以当前字符串后缀为字符串的子字符串存在
                ans.insert(p->num);
                vis[p->num] = 1; // 要保证Trie不被破坏,因为要比较的主字符串不止一个
                p = p->fail;
        }
    }
}

char vir[205],str[Maxn];

int main(void)
{
    int n,k,ct = 0;
        scanf("%d",&n); getchar();
        root = new Trie ();
        for (int i = 1; i <= n; ++i) {
            gets(vir);
            insert_ (vir,i);
        }

        getfail();

        scanf("%d",&k); getchar();
        for (int i = 1; i <= k; ++i) {
            memset(vis,0,sizeof(vis));
            gets(str);
            query (str);

            set<int>::iterator it = ans.begin(); // set里头可能会有0存在
            if((*it == 0 && ans.size() > 1) || (*it > 0 && ans.size() > 0)) {
                printf("web %d:",i);
                for (; it != ans.end(); ++it) {
                    if(*it == 0) continue;
                    cout << " " << *it;
                }
                cout << endl;
                ct++;
            }
            ans.clear();  
        }
        printf("total: %d\n",ct);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/godleaf/article/details/81302850
今日推荐