题目大意

有N个病毒,病毒由A-Z字母构成,N个病毒各不相同。给出一段程序P,由A-Z字母构成,若病毒在在程序P或者P的逆转字符串P'中存在,则该程序P被该病毒感染。求出程序P被多少种病毒感染。

题目分析

典型的多模式串的字符串匹配问题,考虑使用Trie图。将M个待查的字符串作为模式串插入Trie图中,然后设置前缀指针,构造DFA。 
    判断程序P字符串翻转之后,是否含有某个模式串,一种方法是将P翻转,然后在DFA上查找;另一种是在构造DFA的时候,将模式串翻转,然后插入Trie图中,在匹配母串的时候就不需要将母串翻转了。 
    使用第二种方法需要注意的是,可能有两个模式串互为翻转。在Trie图的node节点中维护信息 pattern_index,若某节点为某个模式串的终止节点,则pattern_index为该模式串的序号(从1开始),若节点不是某个模式串的终止节点,则pattern_index = 0. 考虑两个模式串互为翻转(而且最多有两个模式串互为翻转)的情况,可以将pattern_index的高16bit作为pattern1的index,低16bit作为pattern2的index。

实现的时候,出现了几次超时。主要是重复访问了前缀指针节点。通过如下方法剪枝:

在trie图中遇到一个危险节点N(不一定为终止节点),此时母串遍历到当前位置P,可以确定在P之前,肯定出现了模式串 
    在N第一次被访问的时候,可以通过前缀指针找到N之前的所有模式串(需要不断的找prev,直到node到达根节点,比如 ABCDE中有模式串 BCDE, CDE, DE,需要不断的找前缀指针直到root,来防止遗漏某个模式串)

遇到危险节点N,向前找前缀指针的时候,碰到某个之前被访问过的节点A,即可返回.这是因为: 
    若A为危险节点,则它肯定在第一次被访问的时候就进行和N相同的处理(向前找模式串) 
    若A不是危险节点,在第一次被访问的时候,通过A的前缀指针,前缀指针的前缀指针....能到达的模式串都被找到了。因此之后再次碰到A,直接返回即可。

实现(c++)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
#define LETTERS 26
#define MAX_NODES 500000
#define MAX_VIRUS_LEN 1004
#define MAX_PROGRAM_LEN 5100005
#define MAX_VIRUS_NUM 255
char gProgram[MAX_PROGRAM_LEN];
bool gVirusVisited[MAX_VIRUS_NUM];
int gVirusFindNum;
int gVirusNum;
struct Node{
Node* childs[LETTERS];
Node* prev;
bool danger_node;
int pattern_index;
bool visited; //判断节点是否被访问过 //在trie图中遇到一个危险节点N(不一定为终止节点),此时母串遍历到当前位置P,可以确定在P之前,肯定出现了模式串
//在N第一次被访问的时候,可以通过前缀指针找到N之前的所有模式串
//(需要不断的找prev,直到node到达根节点,比如 ABCDE中有模式串 BCDE, CDE, DE,需要不断的找前缀指针直到root,来防止遗漏某个模式串) //遇到危险节点N,向前找前缀指针的时候,碰到某个之前被访问过的节点A,即可返回
//这是因为,若A为危险节点,则它肯定在第一次被访问的时候就进行和N相同的处理(向前找模式串)
//若A不是危险节点,在第一次被访问的时候,通过A的前缀指针,前缀指针的前缀指针....能到达的模式串都被找到了。因此之后
//再次碰到A,直接返回即可。
}; Node gNodes[MAX_NODES];
int gNodeCount;
void Insert(Node* root, char* str, int pat){
char*p = str;
Node* node = root;
while (*p != '\0'){
int index = *p - 'A';
if (node->childs[index] == NULL){
node->childs[index] = gNodes + gNodeCount++;
}
node = node->childs[index];
p++;
}
node->danger_node = true;
if (node->pattern_index == 0)
node->pattern_index = pat;
else{ //有可能两个virus串,互为逆串
node->pattern_index <<= 16;
node->pattern_index |= pat;
}
} void BuildDfa(){
Node* root = gNodes + 1;
for (int i = 0; i < LETTERS; i++){
gNodes[0].childs[i] = root;
}
root->prev = gNodes;
gNodes[0].prev = NULL;
queue<Node*> Q;
Q.push(root);
while (!Q.empty()){
Node* node = Q.front();
Q.pop();
Node* prev = node->prev;
Node* p;
for (int i = 0; i < LETTERS; i++){
if (node->childs[i]){
p = prev;
while (p && !p->childs[i]){
p = p->prev;
}
node->childs[i]->prev = p->childs[i];
if (p->childs[i]->danger_node)
node->childs[i]->danger_node = true;
Q.push(node->childs[i]);
}
}
}
} void FindPatternFromEndPoint(Node* node){
do{
if (node->visited) //若该节点之前被访问过,则直接返回
return; node->visited = true;
if (node->pattern_index){
if (node->pattern_index <= gVirusNum){
if (! gVirusVisited[node->pattern_index]){
gVirusVisited[node->pattern_index] = true;
gVirusFindNum++;
}
}
else{ //两个模式串互为逆串
int virus1 = node->pattern_index & 0xFFFF;
int virus2 = node->pattern_index >> 16;
if (!gVirusVisited[virus1]){
gVirusVisited[virus1] = true;
gVirusFindNum++;
}
if (!gVirusVisited[virus2]){
gVirusVisited[virus2] = true;
gVirusFindNum++;
}
}
}
node = node->prev;
} while (node->prev);
} void Search(Node* root, char* str, int n){
char*p = str;
Node* node = root;
while (*p != '\0'){
int index = *p - 'A';
if (gVirusFindNum >= n){
return;
}
while (node && node->childs[index] == NULL){
node = node->prev;
}
node = node->childs[index];
if (node->danger_node){
FindPatternFromEndPoint(node);
}
p++;
}
} int main(){
int cas;
scanf("%d", &cas);
char virus[MAX_VIRUS_LEN];
while (cas--){
int n;
memset(gNodes, 0, sizeof(gNodes));
gNodeCount = 2;
memset(gVirusVisited, false, sizeof(gVirusVisited));
gVirusFindNum = 0; scanf("%d", &n);
gVirusNum = n;
getchar();
for (int i = 0; i < n; i++){
scanf("%s", virus);
Insert(gNodes + 1, virus, i + 1);
reverse(virus, virus + strlen(virus));
Insert(gNodes + 1, virus, i + 1);
}
BuildDfa();
getchar();
char tmp;
int k = 0;
for (;;){
scanf("%c", &tmp);
if (tmp == '\n')
break; if (tmp != '['){
gProgram[k++] = tmp;
}
else{
int num;
scanf("%d", &num);
scanf("%c", &tmp);
for (int i = 0; i < num; i++){
gProgram[k++] = tmp;
}
scanf("%c", &tmp);
}
}
gProgram[k++] = '\0';
Search(gNodes + 1, gProgram, n); printf("%d\n", gVirusFindNum);
}
return 0;
}

poj_3987 Trie图的更多相关文章

  1. 【BZOJ-2938】病毒 Trie图 + 拓扑排序

    2938: [Poi2000]病毒 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 609  Solved: 318[Submit][Status][Di ...

  2. 【hihoCoder】1036 Trie图

    题目:http://hihocoder.com/problemset/problem/1036 给一个词典dict,词典中包含了一些单词words.要求判断给定的一个文本串text中是否包含这个字典中 ...

  3. 【hihoCoder 1036】Trie图

    看了一下简单的$Trie图$,调模板调啊调一连调了$2h$,最后发现$-'a'$打成$-'A'$了hhh,有种摔键盘的冲动. $Trie图$是$Trie树$上建立“前缀边”,不用再像在$Trie树$上 ...

  4. 字符串 --- KMP Eentend-Kmp 自动机 trie图 trie树 后缀树 后缀数组

    涉及到字符串的问题,无外乎这样一些算法和数据结构:自动机 KMP算法 Extend-KMP 后缀树 后缀数组 trie树 trie图及其应用.当然这些都是比较高级的数据结构和算法,而这里面最常用和最熟 ...

  5. Trie图和Fail树

    Trie图和AC自动机的区别 Trie图是AC自动机的确定化形式,即把每个结点不存在字符的next指针都补全了.这样做的好处是使得构造fail指针时不需要next指针为空而需要不断回溯. 比如构造ne ...

  6. hdu2457 Trie图+dp

    hdu2457 给定n个模式串, 和一个文本串 问如果修改最少的字符串使得文本串不包含模式串, 输出最少的次数,如果不能修改成功,则输出-1 dp[i][j] 表示长度为i的字符串, 到达状态j(Tr ...

  7. Trie图

    AC自动机是KMP的多串形式,当文本串失配时,AC自动机的fail指针告诉我们应该跳到哪里去继续匹配(跳到当前匹配串的最长后缀去),所以AC自动机的状态是有限的 但是AC自动机具有不确定性, 比如要求 ...

  8. CF 291E. Tree-String Problem [dfs kmp trie图优化]

    CF291E 题意:一棵树,每条边上有一些字符,求目标串出现了多少次 直接求目标串的fail然后一边dfs一边跑kmp 然后就被特殊数据卡到\(O(n^2)\)了... 因为这样kmp复杂度分析的基础 ...

  9. AC自动机相关Fail树和Trie图相关基础知识

    装载自55242字符串AC自动机专栏 fail树 定义 把所有fail指针逆向,这样就得到了一棵树 (因为每个节点的出度都为1,所以逆向后每个节点入度为1,所以得到的是一棵树) 还账- 有了这个东西, ...

随机推荐

  1. linux 中的进程wait()和waitpid函数,僵尸进程详解,以及利用这两个函数解决进程同步问题

    转载自:http://blog.sina.com.cn/s/blog_7776b9d3010144f9.html 在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / wait ...

  2. android学习日记01--综述

    开个博客,写点关于Android的知识,希望温故而知新吧! 一.总体框架 先上一张google提供官方的Android框架图: Android系统架构由5部分组成,分别是:Linux Kernel.A ...

  3. iOS边练边学--view的封装

    一.view封装的思路: *如果一个view内部的子控件比较多,一般会考虑自定义一个view,把它内部的子控件的创建屏蔽起来,不让外界关心 *外界可以传入对应的模型数据给view,view拿到模型数据 ...

  4. OC基础--常用类的初步介绍与简单实用之NSString

    一.NSString:不可变字符串/NSMutableString:可变字符串 1>字符串的常用创建方式: (1)NSString *s1 = @"Chaos"; (2)NS ...

  5. FFmpeg API变化

    可以查看doc目录下的APIchanges和根目录下的Changelog 去掉了ffserver程序   'avcodec_register_all' is deprecated 还有av_regis ...

  6. python中copy 与 '=' 的区别

    当你a=1000的时候a指向一个新的类,内容为1000,而b仍然指向原来指向的内容,因为你没有叫它指向其他内容.你使用=符号,使得a和b指向同一个内容,而copy则是将b的内容复制后让c指向这个拷贝的 ...

  7. C语言 格式化输出--%m.n

    格式字符:格式字符用以指定输出项的数据类型和输出格式. ①d格式:用来输出十进制整数(int).有以下几种用法: %d:按整型数据的实际长度输出. %m.nd:m为指定的输出字段的宽度,n定义为实际输 ...

  8. (转)sqlite3生成lib遇到的问题

    今天想用一用sqlite,但是下载后发现只有DLL,没有LIB,只能自己生成了.在H:/Program Files/Microsoft Visual Studio 8/VC/bin里面有个lib.ex ...

  9. linux -- Ubuntu Server 安装图形界面

    1.连接网络,你一定要确保网络通畅,如果你和我一样使用Wireless,那先找根网线插上,因为下面的安装都要通过网络下载组件的. 2.进入图形界面的命令是startX,敲击后会有安装xinit的提示. ...

  10. JVM基础知识与配置

    1 怎样设置JVM内存设置 本文向大家简介一下进行JVM内存设置几种方法.安装Java开发软件时.默认安装包括两个目录,一个JDK(Java开发工具箱).一个JRE(Java执行环境,内含JVM),当 ...