题目大意
有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 != ' '){ 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 != ' '){ 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 == ' ') 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++] = ' '; Search(gNodes + 1, gProgram, n); printf("%d ", gVirusFindNum); } return 0; }