poj_3987 Trie图

题目大意

有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;
}
时间: 2024-10-07 07:03:32

poj_3987 Trie图的相关文章

hiho一下 第二周&amp;第四周:从Trie树到Trie图

hihocoder #1014 题目地址:http://hihocoder.com/problemset/problem/1014 hihocoder #1036 题目地址: http://hihocoder.com/problemset/problem/1036 trie图其实就是trie树+KMP #1014trie树 #include<stdio.h> #include <algorithm> #include <cstring> #include <str

Trie图 &amp; AC自动机初学(1)

题目来源于:Hihocoder 时间限制:20000ms 单点时限:1000ms 内存限制:512MB 描述 前情回顾 上回说到,小Hi和小Ho接受到了河蟹先生伟大而光荣的任务:河蟹先生将要给与他们一篇从互联网上收集来的文章,和一本厚厚的河蟹词典,而他们要做的是判断这篇文章中是否存在那些属于河蟹词典中的词语. 当时,小Hi和小Ho的水平还是十分有限,他们只能够想到:"枚举每一个单词,然后枚举文章中可能的起始位置,然后进行匹配,看能否成功."这样非常朴素的想法,但是这样的算法时间复杂度是

Trie图

DFA 确定性有限状态自动机 DFA确定性有限状态自动机是一种图结构的数据结构,可以由(Q, q0, A, Sigma, Delta)来描述,其中Q为状态集,q0为初始状态,A为终态集合,Sigma为字母表,Delta为转移函数.它表示从唯一一个起始状态q0开始,经过有限步的Delta转移,转移是根据字母表Sigma中的元素来进行,最终到达终态集合A中的某个状态的状态移动.  如图所示是一个终态集合为{"nano"}的DFA.     DFA只能有一个起点而可以有多个终点.每个节点都有

hihoCoder#1036 Trie图

原题地址 看了这篇博文,总算是把Trie图弄明白了 Runtime Error了无数次,一直不知道为什么,于是写了个脚本生成了一组大数据,发现果然段错误了. 调试了一下午,总算闹明白了,为什么呢? 1. 空间超大的变量不要放在函数里,会爆栈,应该弄成全局变量或者是从堆上动态分配. 2. 看清题目的数据范围,一开始我的MAX_NODE设的是1024... 代码: 1 #include <iostream> 2 #include <cstring> 3 4 using namespac

hdu2457 Trie图+dp

hdu2457 给定n个模式串, 和一个文本串 问如果修改最少的字符串使得文本串不包含模式串, 输出最少的次数,如果不能修改成功,则输出-1 dp[i][j] 表示长度为i的字符串, 到达状态j(Trie图中的结点)所需要修改的最少次数 那么dp[0->n][0->size] = INF ,  dp[0][root] = 0,  n代表字符串长度, size代表状态数 那么答案就是  min{dp[n][size]} 我们根据模式串所建的Trie图, 进行模拟构造不包含模式串的字符串 从第一个

【hihoCoder】1036 Trie图

题目:http://hihocoder.com/problemset/problem/1036 给一个词典dict,词典中包含了一些单词words.要求判断给定的一个文本串text中是否包含这个字典中的单词words. 相关基础的理解 1. 与用KMP解决的问题的差别 KMP:输入原串S和一个模式串T,判断T是否出现在S中.通过对T计算next数组,避免原串S的回溯. 现在的问题:输入文本串text和多个单词words,判断words中是否有出现在text中.同样希望输入的text不用进行回溯.

hihocoder 1036 Trie图(AC自动机)

传送门 Description 上回说到,小Hi和小Ho接受到了河蟹先生伟大而光荣的任务:河蟹先生将要给与他们一篇从互联网上收集来的文章,和一本厚厚的河蟹词典,而他们要做的是判断这篇文章中是否存在那些属于河蟹词典中的词语. 当时,小Hi和小Ho的水平还是十分有限,他们只能够想到:“枚举每一个单词,然后枚举文章中可能的起始位置,然后进行匹配,看能否成功.”这样非常朴素的想法,但是这样的算法时间复杂度是相当高的,如果说词典的词语数量为N,每个词语长度为L,文章的长度为M,那么需要进行的计算次数是在N

[hiho 04]Trie图

题目描述 Trie 图就是在 Trie 树上建立 fail 指针,类似于KMP算法中的next数组的作用. 这个数据结构的作用是判断一个字符串中是否包含一组字符串中的任意一个. 结构体定义是这样的: typedef struct trie_node { trie_node *nodes[26]; trie_node *fail = NULL; bool word_end = false; trie_node() { for (int i = 0; i < 26; i++) { nodes[i]

【Trie图】BZOJ3940-[Usaco2015 Feb]Censoring

[题目大意] 有一个匹配串和多个模式串,现在不断删去匹配串中的模式串,求出最后匹配串剩下的部分. [思路] 众所周知,KMP的题往往对应着一道AC自动机quq.本题同BZOJ3942(KMP),这里改成AC自动机即可. 我一开始写了原始的AC自动机,写挂了.后来思考了一下,应当用Trie图,机智地1A. 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm&