神奇的C代码之字符串数组的初始化

文章讨论了字符数组初始化的不同方式,包括使用字符串字面量、逐个初始化元素以及使用初始化列表。一个不寻常的代码示例导致系统崩溃,原因是MMU未开启,使得地址直接映射为物理地址,任何变量的改变都会严重影响系统。解决这类问题需要检查MMU、缓存配置,确保正确处理字节对齐问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符串数组的初始化方式有以下几种:

使用字符串字面量初始化数组,可以使用字符串字面量来初始化字符串数组。

例如:

char str[] = "hello world";

这将创建一个名为str的数组,其大小为12个字节(包括结尾的'\0'字符),并将字符串"hello world"复制到该数组中。

逐个初始化数组元素也可以逐个初始化数组元素。

例如:

char str[5];
str[0] = 'h';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';

这将创建一个名为str的数组,其大小为5个字节,并将"h"、"e"、"l"、"l"、"o"依次复制到该数组中。

使用初始化列表初始化数组,在C99标准中,可以使用初始化列表来初始化数组。

例如:

char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};

这将创建一个名为str的数组,其大小为6个字节,并将"h"、"e"、"l"、"l"、"o"、"\0"依次复制到该数组中。

需要注意的是,使用初始化列表初始化数组时,如果没有显式地指定数组大小,编译器将根据初始化列表中的元素个数自动计算数组大小。如果初始化列表中的元素个数超过了数组大小,编译器将会发出警告或错误。因此,最好显式地指定数组大小,以避免这种问题。

遇到的神奇代码之字符两字节对齐导致系统奔溃

char tbuf[] = "hello";

软件代码中定义了如此一行的代码,然后编译完成之后,操作系统无法正常工作。根据初始化的方式,这种定义毫无问题,但是出现的结果却令人大跌眼镜。

然后修改代码

char tbuf[10];
strcpy(tbuf,"hello");

编译完成系统运行正常。

不是很理解这个char tbuf[] = "hello"。对操作系统影响为何如此巨大。就将两种情况的代码反汇编出来。结果如下所示,仅仅是修改了一行代码对编译器产生的影响巨大。

最终查到的结果是:mmu未成功开启导致系统内跑的所有地址都是物理地址,所以在改变一个变量的同时,物理地址发生偏移,整个编译看起来就会差异巨大。

在遇到字节对齐的问题时---要去检查系统的mmu icache dcache---,前后耗费时间有点长。

``` #define _CRT_SECURE_NO_WARNINGS #include <vector> #include <algorithm> #include <unordered_map> #include <tuple> #include <queue> #include <string> #include <cstring> #include<cstdio> using namespace std; using ll = long long; #define N 10000005 int trie[N][4], fail[N]; char cm[100005][100],t[N]; bool vis[N]; int n, m; int cnt = 0, root = 0; int calc(char a) { if (a == 'N')return 0; else if (a == 'S')return 1; else if (a == 'W')return 2; else return 3; } void insert(char* a) { int now = root; int n = strlen(a); for (int i = 0; a[i] != '\0'; i++) { int bit = calc(a[i]); if (!trie[now][bit])trie[now][bit] = ++cnt; now = trie[now][bit]; } } void buildfail() { queue<int>line; fail[root] = root; for (int i = 0; i < 4; i++) { if (trie[root][i]) { fail[trie[root][i]] = root; line.push(trie[root][i]); } } while (line.size()) { int now = line.front(); line.pop(); for (int i = 0; i < 4; i++) { if (!trie[now][i])trie[now][i] = trie[fail[now]][i]; else { fail[trie[now][i]] = trie[fail[now]][i]; line.push(trie[now][i]); } } } } void search() { int now = root; for (int i = 0; t[i]!='\0'; i++) { int index = calc(t[i]); now = trie[now][index]; int temp = now; while (temp != root) { if (!vis[temp]) vis[temp] = true; temp = fail[temp]; } } } int nizhao(char* a) { int now = root; int res = 0; for (int i = 0; a[i] != '\0'; i++) { int bit = calc(a[i]); now = trie[now][bit]; if (vis[now])res++; } return res; } int main() { scanf("%d%d", &n, &m); scanf("%s", t); for (int i = 0; i < m; i++) { scanf("%s", cm[i]); insert(cm[i]); } buildfail(); search(); for (int i = 0; i < m; i++) printf("%d\n", nizhao(cm[i])); }```原题来自:JSOI 2012 在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河。相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中。老人们说,这是玄武神灵将天书藏匿在此。 很多年后,人们终于在进香河地区发现了带有玄武密码的文字。更加神奇的是,这份带有玄武密码的文字,与玄武湖南岸台城的结构有微妙的关联。于是,漫长的破译工作开始了。 经过分析,我们可以用东南西北四个方向来描述台城城砖的摆放,不妨用一个长度为 N 的序列来描述,序列中的元素分别是 E,S,W,N,代表了东南西北四向,我们称之为母串。而神秘的玄武密码是由四象的图案描述而成的 M 段文字。这里的四象,分别是东之青龙,西之白虎,南之朱雀,北之玄武,对东南西北四向相对应。 现在,考古工作者遇到了一个难题。对于每一段文字,其前缀在母串上的最大匹配长度是多少呢? 【输入】 第一行有两个整数,N 和 M,分别表示母串的长度和文字段的个数; 第二行是一个长度为 N 的字符串,所有字符都满足是 E,S,W 和 N 中的一个; 之后 M 行,每行有一个字符串,描述了一段带有玄武密码的文字。依然满足,所有字符都满足是 E,S,W 和 N 中的一个。 【输出】 输出有 M 行,对应 M 段文字。 每一行输出一个数,表示这一段文字的前缀与母串的最大匹配串长度。
03-24
<think>好的,我现在要仔细看看这个问题。题目大意是说,给一个母串,然后多个查询字符串,每个查询要找它的前缀在母串中的最长匹配长度。比如,如果查询字符串是"EN",那么要找出它在母串中出现的最长的前缀,比如"E"或者"EN"是否存在,取最长的那个长度。 那程序代码用了什么方法呢?看起来像是AC自动机。因为代码里有Trie树的结构,还有构造失败指针的函数buildfail,以及search函数和nizhao函数。可能这个程序是把所有查询的字符串插入到Trie树中,然后利用AC自动机来在母串中匹配,标记哪些节点被访问过。然后对于每个查询字符串,遍历它在Trie中的路径,统计被标记的最深的节点,从而得到最大匹配长度。 首先看代码的结构。主函数读取n和m,然后读入母串t。接着读m个模式串,每个都插入到Trie中。然后构建失败指针,执行search函数,之后对每个模式串调用nizhao函数输出结果。 insert函数是标准的Trie插入操作。每个字符被转换为0-3的数字,对应N、S、W、E。然后沿着Trie树插入。 buildfail函数是构造AC自动机的失败指针。这里用了BFS的方式,每个节点的失败指针指向父节点失败指针对应的子节点,或者继续跳转直到根。这里在构建的时候,对每个节点处理其子节点,如果没有子节点,则直接指向失败指针的对应子节点,这样在匹配的时候可以自动跳转,不需要回溯。这一步是AC自动机的优化,称为路径压缩或者转移优化。 search函数的作用是遍历母串t,在Trie树中进行匹配。同时,每走到一个节点,就沿着失败指针链向上走,标记所有经过的节点。这可能是因为AC自动机的每个节点代表某个模式的前缀,而母串中出现的所有可能的前缀都会被标记。例如,如果母串中有某个模式的前缀出现,那么该节点会被标记为vis[temp]=true。 然后nizhao函数,对于给定的模式串,沿着Trie树走,每一步检查当前节点是否被访问过(即在母串中出现过),记录最大的匹配位置。比如,当处理模式串的每个字符时,走到某个节点,如果该节点被标记过,那么res会被增加,但这里可能记录的是每一步是否被访问过,但需要最大的长度。例如,如果模式串长度为5,每一步检查是否被访问过,那么最大的i+1就是所求的长度。或者,nizhao函数的逻辑可能有问题? 比如nizhao函数中的循环,每走一步,检查当前节点是否被vis标记过。而res每次是累加,这似乎不太对。比如,假设模式是ABC,当走到A的时候,vis为true,那么res加1;走到B的时候,如果vis为true,res加1,此时总共有2,但是可能最大的匹配长度是2。但原题要求的是最大的前缀匹配长度,应该是从第一个字符到第k个字符全部在母串中出现过,所以需要找到最大的k使得该前缀对应的节点被访问过。所以,正确的做法应该是遍历模式串的每个字符,逐步走Trie的节点,并记录最后一个被访问的节点的位置,从而得到最大的k。 但当前nizhao函数的实现是,每走一步,如果当前节点被访问过,则res++。比如,假设模式是长度为3,每个字符对应的节点都被访问过,那么res会是3。但是如果中间某个节点未被访问,后面的即使被访问了,可能res不会累加。例如,模式是"NNN",假设第三个节点未被访问,而前两个被访问过,那么res可能等于2。这样是否正确呢? 可能需要仔细分析代码。例如,nizhao函数中,对于模式串的每个字符,从根节点开始,沿着字符的路径走下去。每一步,如果当前节点now的vis标记为true,则res加1。例如,假设模式是"abc",那么当走到a对应的节点时,如果该节点被访问过,则res加1;走到ab对应的节点时,如果被访问过,再加1;走到abc对应的节点时,如果被访问过,再加1。此时总共有3次加1,所以输出的是3。而题目要求的是最大的前缀长度,也就是连续的前缀中最长的那个。比如,如果前三个字符对应的节点都被访问过,那么最大的前缀是3;如果第二个字符的节点未被访问过,那么只能第一个字符匹配,所以最大是1。但是在代码中,nizhao函数的res每次是累加当前节点是否被访问过。这显然是不正确的。比如,如果模式是"ABC",其中A对应的节点被访问过,B对应的未被访问,而C对应的又被访问过。那么在nizhao函数中,res会在A处加1,B处不加,C处又加,得到res=2,但实际上最大的匹配长度是1,因为只有前缀A是匹配的,B不匹配的话,后续的C即使节点被访问过,但此时整个前缀ABC的前两个字符已经不匹配了,所以C的前缀AB已经不成立。所以这里的代码逻辑有问题。 那这说明原代码可能存在错误。那为什么用户给出的代码能通过题目呢?或者题目是否存在其他条件? 或者,可能我的理解有误。比如,在search函数中,当处理母串t的时候,每个字符会沿着Trie的转移走到某个节点,然后沿着失败指针链标记所有节点。例如,假设母串中存在某个子串等于模式串的前缀,那么该模式串对应的Trie节点会被标记。例如,如果母串中有某个位置,其对应的路径正好是模式的前k个字符,那么在search过程中,该模式对应的节点会被访问到。而nizhao函数中的res统计的是,在模式串的路径上,有多少个节点被标记过,可能这些节点对应的位置是模式的前缀,所以最大的k就是res的值。例如,当模式的前缀中每一个节点都被访问过的话,那么最大的k是模式串的长度。否则,如果某个节点未被访问,则后面的节点即使被访问过,可能对应的并不是连续的前缀。 比如,假设模式是ABCD,其中A、B、D的节点都被访问过,而C的节点未被访问。那么nizhao函数会统计到A和B的节点被访问过,此时res为2,但D节点是否会被访问到?因为当走到C的时候,由于该节点未被访问,所以now可能转移到其他路径,导致后续的D无法走到正确的路径上。这说明nizhao函数的逻辑是错误的。 这可能意味着原代码存在错误,无法正确解决题目要求的问题。那么这说明,原题的正确做法应该是,对于每个查询字符串,找到其所有前缀(比如长度为1、2、...、len)中,最长的那个在母串中出现过的长度。而AC自动机通常用于多模式匹配,找出所有模式串的出现位置。但在此题中,每个模式串的前缀是否在母串中出现过?或者,更准确地说,某个模式串的前缀是否作为母串的某个子串出现? 例如,母串是"ENSW",那么对于模式串"ENN",其前缀"E"、"EN"、"ENN"。如果母串中存在子串等于"E"(存在)、"EN"(存在),但"ENN"不存在。那么最大的匹配长度是2。 原题中的问题转化为,对于每个模式串,找到最大的k,使得该模式的前k个字符构成的字符串是母串的某个子串。这可能与AC自动机的处理方式有关。 那么正确的做法应该是,将所有模式串插入到Trie中,构建AC自动机,然后在母串中遍历,对于母串的每个位置,找到所有以该位置结尾的最长匹配的模式前缀。然后,对于每个模式串,找出其所有前缀中,在母串中出现过的最大长度。 但原代码的处理方式可能不同。在search函数中,当处理母串时,对于每个位置i,在Trie中进行转移,并沿着失败指针链标记所有访问的节点。这可能意味着,所有在母串中出现的子串对应的Trie节点都会被标记。例如,如果母串中存在子串S,那么S对应的Trie节点会被标记。而每个模式的前缀如果是S的子串,那么其对应的节点会被标记。因此,nizhao函数中,对于每个模式串,遍历其路径,记录最后一个被访问的节点,其对应的位置即为最大匹配长度。 但原代码中的nizhao函数是每次遇到被访问的节点就res++,这可能不正确。例如,如果模式是"ABCD",其中前三个字符的节点都被访问过,但第四个未被访问,则res是3,这正确。但是如果模式的前两个节点被访问,第三个未被访问,第四个被访问,那么res会是2+1=3?这时实际的最大匹配长度应该是前两个字符,而第四个的访问可能来自其他路径,比如,如果第四个字符对应的节点是其他路径的节点,那么可能这并不代表模式的前四个字符都出现在母串中。 这说明原代码中的nizhao函数实现有误。正确的做法应该是,沿着模式串的路径走,记录连续访问的最远节点。例如,初始化max_len为0,当走到某个节点时,如果该节点被访问过,则当前长度为i+1(假设i从0开始),然后比较是否比max_len大,若是则更新max_len。这样,最终max_len就是最大的连续前缀长度。 而原代码中的nizhao函数中的res每次是累加的,这可能错误地将非连续的情况计算进去。比如,模式的前缀中,中间某个节点未被访问,但后面的节点被访问的情况会被错误地统计。 这可能意味着原代码是错误的,无法通过所有测试用例。比如,假设有一个模式是"ENSW",其中前两个字符EN对应的节点被访问过,第三个S未被访问,而第四个W对应的节点被访问过。那么按照原代码的nizhao函数,会得到res=3(E、EN被访问,S未被,所以此时在第四个字符的节点是否会被访问?只有当在母串中存在该模式的前四个字符时才会被访问吗?或者,可能在母串中存在其他路径导致该节点被访问?例如,如果母串中存在某个子串,其对应的路径正好是模式的前四个字符,或者该节点被其他模式的前缀所共享,并被访问过,那么即使当前模式的前三个字符未被访问,第四个的节点可能被标记。这时候,nizhao函数的res会累加1,导致结果错误。 这说明原代码的逻辑存在问题,不能正确求解问题。正确的做法应该是在遍历模式串的每个字符时,检查当前路径上的所有前缀是否被访问过。例如,假设模式是ABCD,那么当处理到第i个字符时,检查对应的节点是否被访问过,如果被访问过,则更新最大长度为i+1。这样,可以找到最大的连续前缀长度。 那原代码中的nizhao函数为什么返回的是res的值呢?例如,当模式串的每个字符对应的节点都被访问过时,res的值等于模式串的长度,这正确。但如果中间某个节点未被访问过,那么后续的节点即使被访问过,res也不会增加,因此最终结果等于最后一个被访问过的位置的前缀长度。例如,假设模式是ABCD,前三个节点被访问,第四个未被访问,则res=3;如果前两个被访问,第三个未被,第四个被访问,则res是2,因为第三个未被访问的话,走到第四个的时候,now可能已经不是原来的路径了? 比如,当处理第三个字符的时候,由于第三个字符对应的节点未被访问,那么trie[now][bit]可能已经被路径压缩优化过,可能跳转到其他路径。此时,第四个字符的节点可能已经不是原来的路径,所以此时即使该节点被访问过,但并不是模式串的第四个字符对应的节点,所以是否会影响结果? 这似乎非常复杂。可能需要通过举例来说明。假设母串是"ENSW",模式串是"ENSW"。在插入到Trie后,路径是E->N->S->W。假设在search函数中,当处理母串的每个字符时,会沿着路径走,并标记每个节点。此时,nizhao函数返回4,正确。但如果母串是"ENW",那么对于模式"ENSW",在母串中的路径是E->N->W。此时,原模式中的第三个字符是S,所以当处理母串的第三个字符W时,trie的路径可能不是原来的路径。假设在构建失败指针的时候,如果原路径不存在,则会跳转到失败指针的节点。例如,当处理到第三个字符W时,原来的路径是E->N,此时在Trie中可能没有S的转移,所以会跳转到失败指针指向的节点,假设是根节点,那么此时会转移到根节点的W转移。如果根节点的W转移存在(例如其他模式中有W),那么此时的now会是该节点。如果该节点被访问过,那么模式串的第四个字符W对应的节点会被统计到,导致nizhao函数认为该模式的前四个字符中的第四个被访问过,即使前三个未被完全访问。这显然会导致错误。 这说明原代码中的nizhao函数逻辑错误,无法正确统计连续前缀的最大长度。正确的做法应该是在遍历模式串的每个字符时,沿着正确的路径走,并在每一步检查当前节点是否被访问过。如果当前节点被访问过,那么当前的前缀是匹配的,可以记录长度。否则,后面的字符即使对应的节点被访问过,但因为路径已经改变,所以无法形成更长的前缀。 例如,正确的方法是:对于模式串的每个字符i(从0到len-1),沿着Trie的路径走到对应的节点,如果该节点被访问过,那么当前的前缀长度是i+1,并记录最大的值。如果在某一步无法继续沿着路径走(即节点不存在),则直接退出循环,因为后续的前缀也不可能更长。或者,即使节点存在,但未被访问过,此时无法形成更长的前缀,但后面的字符可能对应的节点被访问过,但由于路径不同,所以可能不是该模式的前缀对应的节点。 因此,正确的做法应该是,沿着模式串的路径在Trie中走,并记录每一步是否被访问过,取最大的i+1作为结果。例如: int max_len = 0; int now = root; for (int i=0; a[i]; i++) { int bit = calc(a[i]); now = trie[now][bit]; if (now == 0) break; // 没有对应的节点,无法继续 if (vis[now]) { max_len = i+1; } } return max_len; 这样,只有当沿着模式串的路径走到某个节点,并且该节点被访问过时,才会更新最大长度。例如,假设模式是ABCD,前三个节点被访问过,那么max_len是3;如果第四个节点也被访问过,则max_len是4。如果中间某个节点未被访问,后续的节点即使被访问过,但路径可能已经改变,无法形成更长的前缀。 原代码中的nizhao函数实现如下: int nizhao(char* a) { int now = root; int res = 0; for (int i = 0; a[i] != '\0'; i++) { int bit = calc(a[i]); now = trie[now][bit]; if (vis[now])res++; } return res; } 这里的问题在于,res是每次遇到被访问的节点就增加,不管是否是路径上的连续前缀。比如,假设模式是"ENE",其中第一个E对应的节点被访问过(res=1),第二个N的节点未被访问过(res不变),第三个E的节点被访问过(res=2)。这时返回的是2,但正确的前缀最大匹配长度应该是1,因为只有第一个字符的前缀被匹配,第三个字符对应的节点可能属于其他路径,比如,母串中存在某个子串"E",那么第三个节点可能被标记,但此时路径可能不是模式的前三个字符的路径。 因此,原代码的nizhao函数逻辑错误,导致结果不正确。这可能意味着该代码无法通过所有测试用例。例如,当母串中存在某个模式的部分字符的节点被其他路径共享时,可能导致错误的结果。 例如,假设母串是"EN",那么search函数会标记路径E(节点1)和 EN(节点2)。现在有一个模式串是"E",则res=1;另一个模式是"EN",res=2;这些都是正确的。但如果有一个模式串是"ENS",其中S对应的节点未被访问过,那么res的值是2,但是该模式的前缀"ENS"的前两个字符是匹配的,所以正确的结果是2。此时nizhao函数返回的res是正确的。 但是,假设有一个模式是"ENES",其前三个字符ENE对应的节点如下:E的节点1,N的节点2,E的节点是否存在?假设插入到Trie的时候,每个模式串都生成对应的节点。假设该模式被插入到Trie,那么路径是E->N->E->S。假设在母串中存在"ENE",那么search函数在遍历母串的每个字符时,走到E节点(标记),然后N节点(标记),然后 E节点(假设母串第三个字符是E,此时走到trie[2][E的bit],即节点3,如果存在的话)。假设母串是"ENE",则路径会被标记。那么模式"ENES"的nizhao函数在走到第三个字符E时,now是节点3,如果该节点被标记,则res会增加到3。此时,返回3,而正确的结果应该是3,因为前三个字符的前缀"ENE"在母串中出现过。这情况下,原代码是正确的。 但是,如果有另一个例子,母串中有某个子串导致模式的前缀中的部分节点被标记,但路径不连续。例如,母串是"E",那么search函数会标记节点1(E)。现在有一个模式串是"EN",插入到Trie中。在nizhao函数中,处理到第一个字符E时,now是节点1,被标记,res=1。处理到第二个字符N时,trie[1][N的bit]是否存在?假设原Trie中没有其他模式,所以插入"EN"的时候,会生成节点2。此时,now走到节点2。在search函数中,母串是"E",所以在处理第一个字符时,走到节点1,标记。然后沿着失败指针链向上走,假设失败指针是根节点,所以不会标记其他节点。此时,节点2未被标记。所以,处理到第二个字符N时,now是节点2,但vis[now]为false,res不增加。所以,返回1,正确。 所以在这种情况下,原代码是正确的。那什么时候会出现错误的情况呢? 考虑以下情况:母串是"EW",而有一个模式是"EW",另一个模式是"E"。插入到Trie中的路径是E->W(模式1)和E(模式2)。在search函数处理母串"EW"的时候,第一个字符E走到节点1,标记。然后第二个字符W,走到节点2,标记。此时,在nizhao函数处理模式"EW"时,每个字符对应的节点都被标记,res=2,正确。处理模式"E"时,res=1,正确。这是正确的。 那另一个例子:母串是"EWN"。假设有一个模式是"EWN",插入到Trie。那么search函数在母串处理过程中,E->W->N的节点都被标记。现在有一个模式是"EWW",插入到Trie中的路径是E->W->W。在母串处理时,第三个字符是N,所以前两个字符E和W对应的节点被标记。对于模式"EWW",nizhao函数在处理到第一个字符E时,res=1;第二个字符W时,res=2;第三个字符W时,此时trie的当前节点是节点2的W转移是否存在?假设原模式"EWW"插入时,节点2的W转移不存在,所以会创建节点3。在母串处理到第二个字符W时,此时在search函数中,处理到母串的第二个字符W时,走到节点2。然后,当处理到第三个字符N时,now会走到trie[2][N的bit]。假设原模式中没有该转移,则会跳转到fail[now]的N转移,假设fail[now]是根节点。如果根节点的N转移不存在,则now=0。所以,此时在母串中,第三个字符N的处理不会影响节点3(因为模式"EWW"的第三个字符是W,对应的节点是3)。所以,在母串处理过程中,节点3未被访问。因此,在nizhao函数处理模式"EWW"时,第三个字符W对应的节点3未被访问,所以res=2。正确,因为母串中并没有出现"EWW"的前三个字符。 这表明原代码在这种情况下是正确的。那问题可能出在另一种情况。比如,假设母串中存在一个较长的模式的前缀,但某个中间节点未被访问,但后续节点被访问。例如,母串是"E",而有一个模式是"ES"。插入到Trie中的路径是E->S。在search函数中,处理母串"E"时,节点1被标记。那么,对于模式"ES",在nizhao函数中,第一个字符E对应的节点1被访问,res=1。第二个字符S时,节点1的S转移是否存在?假设原模式插入时创建了节点2,所以此时now=节点2。而vis[节点2]未被标记,因为母串中没有S字符。所以,res仍为1。正确。 综上,原代码中的nizhao函数是否正确呢?可能我之前的分析是错误的。比如,当沿着模式串的路径走,每一步的now必须严格对应于模式串的路径。因为,在构建Trie的时候,如果有某个模式串的路径不存在,那么插入的时候会创建节点。在buildfail函数中,对于每个节点的转移,如果不存在的话,会指向fail节点的对应转移。这可能使得在匹配母串的时候,某些路径被自动跳转,导致标记的节点可能不是模式串的路径上的节点。例如,在模式串的某个节点未被创建的情况下,母串的匹配可能会跳转到其他路径,导致该节点被标记,但该节点并不属于模式串的路径。此时,nizhao函数中的now变量可能走到其他路径的节点,从而错误地认为该节点属于模式串的前缀路径。 例如,假设模式串是"ABC",插入到Trie中的路径是A->B->C。另一个模式串是"ABD",插入到Trie中的路径是A->B->D。在buildfail函数中,假设当处理到节点B时,对于转移C和D,如果有其他模式导致trie[B][C]存在的话。但假设原问题中,母串是"ABD"。在search函数中,当处理到第三个字符D时,会走到节点B的D转移对应的节点,假设是节点3。该节点会被标记。此时,对于模式串"ABD",其nizhao函数返回3,正确。但对于模式串"ABC",其路径是A->B->C。在母串处理过程中,第三个字符是D,所以在处理到该字符时,trie[B][D]存在吗?假设原模式中不存在,那么根据buildfail函数的逻辑,trie[B][D]会被设置为trie[fail[B]][D]。假设fail[B]是根节点,而根节点的D转移不存在(即没有其他模式以D开头),则trie[B][D]=0。此时,当母串的第三个字符D被处理时,now会变为0。所以,节点C(模式串"ABC"的第三个字符对应的节点)不会被标记。因此,对于模式"ABC",nizhao函数返回2(A和B的节点被标记),正确。 这表明原代码在这种情况下是正确的。那何时会出现问题? 假设有一个模式串是"A",另一个是"AB"。母串是"AB"。在插入到Trie后,两个模式被插入,路径分别是A(节点1),A->B(节点2)。在search函数处理母串时,第一个字符A到达节点1,标记。第二个字符B到达节点2,标记。此时,对于模式"A",nizhao返回1;对于模式"AB",返回2。正确。 另一个例子,假设有一个模式串是"AB",另一个是"ABC"。母串是"ABC"。插入到Trie中的路径是A->B(节点2),A->B->C(节点3)。在search函数处理母串时,三个字符依次处理,节点1、节点2、节点3都被标记。所以,对于模式"AB",返回2;对于模式"ABC",返回3。正确。 这说明原代码的nizhao函数在这种情况下是正确的。那么问题可能出在,当模式串的路径在Trie中被其他模式的失败指针处理时,导致节点被错误地标记? 比如,假设模式串是"ABCD"和"XY"。母串是"AXY"。插入两个模式后,构建Trie。在search函数处理母串时: 第一个字符A,走到模式"ABCD"的A节点(节点1),标记。失败指针链处理节点1的父节点,假设失败指针是根节点,所以标记节点1。 第二个字符X,此时在节点1的X转移可能不存在,所以根据buildfail的逻辑,会跳转到fail[1](根节点)的X转移。假设没有其他模式以X开头,则根节点的X转移不存在,所以now=0。此时,无法继续,标记的节点为0的失败指针链,但根节点的失败指针是自身,所以循环结束。此时,没有新的节点被标记。 第三个字符Y,同样now=0,无法标记。 此时,对于模式"XY",其路径是X->Y。但母串中的XY子串是否被处理?因为母串的第二个字符是X,但此时now是0,所以X的转移可能不存在,所以now会保持为0。此时,在search函数中,处理到X时,now是0的X转移,可能为0,所以没有节点被标记。所以,模式"XY"的nizhao函数返回0,正确,因为母串中没有XY这个子串。 这说明原代码在这种情况下是正确的。综上,原代码可能在某些情况下是正确的,那为什么用户给出的代码能通过题目?或者原题的测试数据可能不够强,或者我的分析有误? 可能我的分析中存在误解。让我们再仔细看一下原代码的search函数。在search函数中,对于每个字符,沿着Trie转移,然后沿着失败指针链向上走,标记所有经过的节点。例如,假设母串中的某个子串对应某个模式的前缀,那么该前缀的节点会被标记。同时,如果该子串的失败指针链上的节点对应其他模式的前缀,那么这些节点也会被标记。因此,所有在母串中出现过的模式的前缀都会被标记。这可能包括其他模式的前缀,但这些前缀可能不是当前模式的前缀。 例如,假设有一个模式是"ABC",另一个是"BC"。母串是"ABC"。在插入这两个模式后,构建AC自动机。在search函数处理到第三个字符C时,对于模式"ABC"的节点C被标记,同时失败指针链可能包括模式"BC"的节点。因此,模式"BC"的节点也被标记。对于模式"BC",在nizhao函数中,会沿着B->C的路径走,假设这两个节点都被标记,因此返回2。这正确,因为母串中的"BC"子串确实存在。 但是,对于模式"ABC"的节点C是否属于模式"BC"的前缀?可能不属于,因为模式"BC"的前缀是B和 BC。这可能需要更深入的分析。 这表明,原代码中的search函数会标记所有可能的匹配节点,包括失败指针链上的节点。这可能覆盖了模式的前缀的所有可能出现情况。例如,在母串中出现某个模式的前缀,或者该前缀的某个后缀(通过失败指针链)。 例如,母串中的某个子串可能与某个模式的前缀通过失败指针链连接。这可能导致原代码中的nizhao函数统计到该节点的存在,从而返回正确的最大长度。 现在回到原问题,题目要求的是找到模式的前缀在母串中的最长匹配长度。也就是,对于模式的前缀s_k(前k个字符),s_k必须作为母串的一个子串出现。这可能与AC自动机的处理方式一致,因为AC自动机可以找出所有模式的前缀在母串中的出现。因此,原代码中的search函数标记所有在母串中出现的模式的前缀节点,而nizhao函数统计模式路径上被标记的节点的数量。这可能正确,因为如果路径上的某个节点被标记,说明该前缀在母串中作为某个子串出现过。例如,模式的前k个字符对应的节点被标记,说明该前缀出现在母串的某个位置。因此,最大k即为所求。 那为什么原代码中的nizhao函数是累加res?例如,模式的前缀长度是k,那么每个前缀对应的节点被标记的话,res会累加k次。例如,假设模式的前缀长度是3,三个节点都被标记,则res=3。这是正确的。但是,如果某个前缀的节点未被标记,而后续的节点被标记,那么res会增加,导致结果错误吗? 比如,假设模式是"ABCDE",其前三个节点未被标记,而第四个节点被标记。那么res会是1,而正确的结果是0,因为前四个字符的前缀并未在母串中出现。这可能吗?或者,是否只有当前缀的路径上的所有节点都被标记,才能保证该前缀存在? 不,每个节点被标记的条件是,该节点对应的字符串在母串中出现过。例如,节点对应的是某个模式的前缀,而该前缀在母串中出现过。因此,如果模式的前缀s_k对应的节点被标记,那么说明s_k在母串中出现过。不管之前的节点是否被标记,只要当前节点被标记,就说明该前缀存在。因此,原代码中的nizhao函数累加被标记的节点数,可能得到正确的结果。例如,假设模式的前缀长度是5,其中第三个和第五个节点被标记。那么res的值是2,对应的最大匹配长度是5?或者,是5吗?或者,是3和5中的较大者? 哦,这可能取决于每个前缀节点是否被标记。例如,第三个节点被标记,说明前三个字符的前缀存在。第五个节点被标记,说明前五个字符的前缀存在。因此,最大匹配长度是5。但原代码中的res会累加两次,导致结果错误。例如,在第五个字符对应的节点被标记的情况下,前四个可能未被标记,但第五个被标记,说明该前缀存在,所以最大长度是5。但原代码中的res会累加1(第五个节点被访问过),返回1。这显然错误。 这说明原代码的nizhao函数存在严重错误,无法正确处理这种情况。例如,假设模式的前缀k的节点被标记,而前面的节点未被标记,这可能吗?或者说,如果某个节点被标记,那么其父节点一定也被标记? 这可能不成立。例如,假设母串中有一个子串对应模式的前缀k,那么该子串的路径对应的节点会被标记。同时,在search函数中,每个节点被访问时,会沿着失败指针链标记所有上级节点。因此,如果某个节点被标记,那么它的失败指针链上的所有节点也被标记。这可能意味着,当模式的前缀k的节点被标记时,其所有父节点也被标记。例如,假设模式的前缀k的节点在Trie中的路径是A1->A2->...->Ak。在母串中出现过该前缀k,那么在search函数中,当处理到该子串时,走到Ak节点,并沿失败指针链向上标记所有节点。假设失败指针链上的节点可能包括父节点Ak-1,Ak-2,等等。因此,Ak的标记会导致父节点也被标记。因此,当Ak被标记时,其路径上的所有节点A1到Ak都会被标记。这可能意味着,如果模式的前缀k的节点被标记,那么其之前的所有节点也被标记。因此,res的累加结果等于k,而正确的结果也是k。因此,原代码中的nizhao函数返回res,即累加的次数,等于k,是正确的。 那这如何成立呢? 例如,假设母串中存在模式的前缀k,那么在search函数中,处理该子串时,当前节点是Ak。此时,会沿着失败指针链向上标记所有节点。假设失败指针链的路径是 Ak的失败指针,Ak-1的失败指针,等等。这可能包括路径上的所有父节点。例如,如果失败指针链的构建方式导致父节点也被标记的话,那么模式的前缀k的节点被标记时,父节点也都被标记。因此,在模式的路径中,前k个节点都被标记。因此,res的累加次数等于k,正确。 例如,假设模式是"ABCD",在母串中出现过。那么,在search函数中,当处理到D时,节点4被标记,然后沿失败指针链向上。假设失败指针链中的节点可能包括父节点3、2、1,这些节点可能已经被之前的处理标记过。或者,失败指针链可能跳转到其他分支,导致并非所有父节点都被标记。这取决于失败指针的构造。 这里需要理解buildfail函数的逻辑。在buildfail函数中,每个节点的失败指针指向的是当前最长后缀对应的节点。例如,节点Ak的失败指针指向的节点对应的是模式的前缀的最长后缀。例如,对于模式"ABCD",节点A4的失败指针可能指向某个其他模式的前缀,比如,如果存在模式"BCD",则可能指向该模式的前缀节点。或者,如果不存在其他模式,则失败指针指向根节点。 当在search函数中处理到母串中的字符,导致走到节点Ak时,会沿着失败指针链向上走,标记所有经过的节点。因此,如果Ak的失败指针是某个节点B,那么节点B也会被标记。节点B的失败指针是另一个节点C,也会被标记,依此类推。这可能意味着,节点Ak及其所有失败指针链上的节点都被标记。但这些节点可能对应不同的模式前缀。 假设在模式"ABCD"中,节点A4的失败指针指向根节点。那么,在search函数处理到Ak时,会标记A4,然后根节点。而根节点的失败指针是自身,循环结束。因此,父节点A3不会被标记,除非母串中有一个更短的前缀被处理时触发了标记。 这可能意味着,原代码中的假设(即如果某个节点被标记,那么其所有父节点也被标记)并不成立。因此,原代码的nizhao函数可能错误地累加非连续的前缀节点。 例如,考虑模式串"ABCDE",假设母串中存在子串"ABCDE"。那么,在search函数中,处理到每个字符时,会依次标记节点1(A)、2(B)、3(C)、4(D)、5(E)。此外,每个节点的失败指针链可能指向根节点或其他节点。假设失败指针链的节点未被其他处理标记,则这五个节点会被标记。在nizhao函数中,处理到每个字符时,节点1、2、3、4、5都被标记,res累加5次,返回5,正确。 现在假设母串中存在子串"BCDE"。此时,模式"ABCDE"的前缀"BCDE"是否会被视为出现?不,因为模式的前缀是A followed by B, C, D, E。母串中的"BCDE"子串对应的是模式的前缀从B开始,但原模式的前缀必须从A开始。因此,在AC自动机中,处理到母串的"BCDE"时,可能不会触发原模式的前缀节点被标记。因此,模式"ABCDE"的前缀不会在母串中被匹配,原代码的nizhao函数返回0,正确。 这表明原代码在这种情况下是正确的。那回到之前的例子,假设母串中存在子串"CDE",而模式是"ABCDE"。此时,原代码的search函数在处理到母串中的C时,可能走到其他模式的节点,或者根节点的转移。此时,原模式的节点3(C)可能不会被标记,因此原代码的nizhao函数在处理到第三个字符C时,节点3未被标记,res不会增加。正确。 综上,原代码的nizhao函数是否正确,取决于在search函数中,模式的前缀节点是否被标记当且仅当该前缀在母串中出现过。而由于在search函数中,每个字符处理时,会标记当前节点及其失败指针链上的所有节点,这可能导致某些不相关的前缀节点被标记,从而影响结果。 例如,假设有一个模式串是"ABCD",另一个是"BCD"。母串中存在子串"BCD"。此时,在插入两个模式后,构建AC自动机。在search函数处理到母串的BCD时,会走到模式"BCD"的节点,并标记其路径上的节点。同时,由于"ABCD"的失败指针可能指向"BCD"的某个节点,因此在处理母串的BCD时,模式"ABCD"的某些节点可能被标记吗? 这可能比较复杂。假设在buildfail的过程中,模式"ABCD"的节点B的失败指针指向根节点,节点C的失败指针指向某个其他节点,比如模式"BCD"的节点C。这可能导致当母串中的BCD被处理时,某些节点被标记,从而使得模式"ABCD"的某些节点也被标记。例如,假设模式"ABCD"的节点C的失败指针指向模式"BCD"的节点 C。当母串中的BCD被处理时,会走到模式"BCD"的节点 C,并标记该节点。因此,在模式"ABCD"的路径上,节点C的失败指针指向的节点被标记,因此在search函数中,当处理到母串中的C时,会走到模式"BCD"的节点C,并标记该节点。此时,对于模式"ABCD",在nizhao函数处理到第三个字符C时,now会走到模式"ABCD"的节点C吗?或者会因为路径转移而走到其他节点? 这取决于Trie树的构建和失败指针的处理。例如,在buildfail函数中,当处理到节点 now的每个子节点i: if (!trie[now][i]) trie[now][i] = trie[fail[now]][i]; 这行代码将trie[now][i]设置为失败指针路径上的对应转移。这实际上是路径压缩,使得在匹配时,无需反复跳转失败指针,可以直接转移。这种优化方式可能导致trie树的结构发生变化,即使某些节点的子节点指向其他分支的节点。 例如,假设模式"ABCD"的节点B(第二个字符)的失败指针指向根节点。此时,trie[B]['C']可能不存在,因此会被设置为trie[fail[B]]['C'],即根节点的'C'转移。假设模式"BCD"的根节点的'B'转移存在,则当处理字符'B'时,会跳转到该节点。这可能比较复杂,可能需要具体例子来分析。 综上,原代码可能在某些情况下产生错误的统计结果,导致无法正确计算最大匹配长度。例如,当母串中的某个子串对应其他模式的前缀,而该前缀是当前模式的前缀的后缀时,可能导致原模式的前缀节点被错误地标记,从而统计出错误的长度。 因此,原代码的正确性依赖于AC自动机的构建方式以及search函数的标记方式。可能原题的正确解法应该使用AC自动机的正向匹配,即在母串中对于每个位置,记录所有可能的模式前缀的最长匹配长度,然后对于每个模式串,取最大的那个。而原代码的实现方式可能不同,导致错误。 但根据用户提供的代码,该代码可能正确通过了原题的测试用例。那可能我的分析有误,或者原代码存在其他隐含的正确性保证。例如,可能每个模式的前缀节点在search函数中被标记当且仅当该前缀在母串中出现过,而不管其出现方式如何。因此,原代码的nizhao函数统计模式路径上的被标记的节点数,即该路径上最大的k,使得前k个字符的前缀在母串中出现过。这可能正确,因为只要该前缀在母串中出现过,无论其是否连续,该节点都会被标记。例如,如果母串中存在某个子串等于模式的前k个字符,无论中间是否被其他字符隔断,那么该前缀节点会被标记。或者,原题的题意可能允许非连续的前缀匹配,但题目实际要求的是连续的前缀。 根据原题的描述,“这段文字的前缀在母串上的最大匹配长度”,例如,前缀必须是连续的,并且作为子串出现在母串中。因此,必须存在母串中的某个子串等于该前缀。 因此,正确的做法是,对于每个模式串的前缀s_k,判断s_k是否是母串的某个子串。最长的这样的k即为所求。 原代码中的做法是将所有模式串构建成AC自动机,然后用母串去匹配,并标记所有被访问过的节点。每个节点被访问过意味着该节点对应的字符串在母串的某个位置作为子串出现过。因此,对于模式串的路径上的每个节点,如果被标记,那么该前缀存在。因此,最大的k就是模式串路径上被标记的最深的节点。例如,沿着模式串的路径走,记录最大的i+1,其中该节点被标记。 因此,正确的nizhao函数应该记录在路径上被标记的节点的最大深度,而不是累加所有被标记的节点数。例如,max_len初始化为0,每走一步,如果当前节点被访问过,则更新max_len为当前步数+1。 所以,原代码中的nizhao函数是错误的,应该修改为: int nizhao(char* a) { int now = root; int max_len = 0; for (int i = 0; a[i] != '\0'; i++) { int bit = calc(a[i]); now = trie[now][bit]; if (vis[now]) { max_len = i + 1; // 因为i是0-based,长度是i+1 } } return max_len; } 这样,只有当沿着模式路径走的过程中,遇到被访问过的节点时,才更新max_len。这样,max_len记录的是路径上最深的被访问的节点对应的前缀长度。这样就能正确得到最大的k。 原代码中的nizhao函数返回的是被访问过的节点数量,这可能不等于最大的k。例如,当路径上的前两个节点被访问过,第三个未被,第四个被访问过,此时max_len是4(如果第四个节点被访问过的话),而原代码的res会是2(前两个节点被访问过,第四个可能不在路径上)。这取决于路径是否被正确跟踪。 因此,原代码存在错误,正确的nizhao函数应该记录最大的i+1,而不是累加。可能原题的测试数据没有覆盖这种情况,导致代码能通过。或者,原代码在某种特定情况下,比如所有被访问的节点都是连续的,导致res的值等于max_len。例如,如果模式的前缀k的节点被访问过,那么其之前的所有节点也被访问过,所以res累加得到k。 根据AC自动机构建失败指针的方式,可能在search函数中,当某个节点被访问过,其所有父节点也被访问过。这可能在特定情况下成立。例如,如果每次匹配母串中的子串,路径上的所有节点都被标记。例如,当母串中的子串是某个模式的前缀k,那么在匹配该子串时,会经过该前缀路径上的所有节点,从而被标记。因此,前缀k的节点被标记意味着前k-1个节点也被标记。因此,res的累加值等于k,正确。 这可能是因为在search函数中,当处理母串的字符时,会沿着Trie的路径走,因此路径上的每个节点都会被访问到。例如,当处理母串中的某个子串S,对应的路径是A1->A2->...->Ak,那么在search函数中,每个字符会触发依次访问这些节点,并沿失败指针链标记。因此,每个节点A1到Ak都会被标记。因此,对于模式串的路径来说,前缀k的节点被标记,意味着前k个节点都被标记。因此,res的累加值等于k,正确。 这可能使得原代码中的nizhao函数返回正确的结果。例如,假设模式的前缀k的节点被标记,那么前k个节点都被标记,所以res的累加次数是k,正确。因此,原代码在这种情况下是正确的。 那这个结论是否成立呢?假设母串中的子串是模式的前缀k,那么在search函数中,处理每个字符时会依次访问路径上的节点。例如,处理第一个字符时,访问A1,并沿失败指针链标记;处理第二个字符时,访问A2,并沿失败指针链标记;依此类推,处理到第k个字符时,访问Ak,并沿失败指针链标记。因此,每个节点A1到Ak都会被标记。因此,在模式路径上的每个节点都被标记,所以res的累加次数是k,正确。 因此,原代码的nizhao函数在这种情况下是正确的。但是,如果母串中的子串不是模式的前缀,而是其他模式的前缀,导致模式路径上的某个节点被标记,而该节点对应的前缀长度小于模式的路径上的节点,那么res的累加可能错误。 例如,假设模式串是"ABCD",而母串中有一个子串是"BCD",属于另一个模式。在search函数中,当处理到'B'字符时,会走到"BCD"的B节点,标记该节点。此时,原模式"ABCD"的B节点是否会被标记?这取决于母串中的字符是否导致原模式的路径被访问。例如,如果母串中的子串是"BCD",那么原模式的路径不会被访问到,所以原模式的B节点不会被标记。因此,原代码的nizhao函数在处理模式"ABCD"时,res的累加次数是0,正确。 但是如果母串中的子串是"ABCD",则所有节点被标记,res累加4次,正确。 因此,原代码的nizhao函数只有在模式路径上的每个节点都被标记时才能正确累加,否则可能会得到错误的结果。例如,如果模式的前缀k的节点被标记,而前面的某个节点未被标记,那么累加的res将小于k。但根据前面的分析,这种情况不会发生,因为如果模式的前缀k的节点被标记,那么路径上的所有节点都被标记。因此,原代码中的nizhao函数是正确的。 这似乎与之前的分析矛盾。为了验证这一点,我需要构造一个测试用例,其中模式路径上的某个节点被标记,而其前面的节点未被标记。这可能吗? 假设母串是"ACD",模式是"ABCD"。插入到Trie后,路径是A->B->C->D。在母串中,处理第一个字符A,走到A节点,标记。处理第二个字符C,此时在A节点的C转移不存在,所以跳转到失败指针的C转移。假设A的失败指针是根节点,而根节点的C转移不存在,所以now=0。处理第三个字符 D,now=0的D转移不存在,所以now=0。因此,模式"ABCD"的A节点被标记,B、C、D节点未被标记。此时,在nizhao函数中,模式"ABCD"的第一个字符A对应的节点被标记,res=1。而后续的字符B、C、D对应的节点未被标记。因此,返回1,正确。因为模式的前缀"A"在母串中出现过,而后续的前缀"AB"等没有出现。 这表明,原代码在这种情况下是正确的。那是否存在其他情况? 假设母串是"ABD",模式是"ABCD"。插入到Trie中,路径A->B->C->D。母串处理时,第一个字符A,节点1被标记。第二个字符B,节点2被标记。第三个字符D,此时B的D转移不存在,跳转到fail[B]的D转移。假设B的失败指针是根节点,而根节点的D转移不存在,所以now=0。此时,在search函数中,处理到D时,now=0,不标记任何节点。因此,模式"ABCD"的节点1和2被标记,节点3和4未被标记。此时,nizhao函数处理模式"ABCD"时,前两个字符的节点被标记,res=2,正确,因为前两个字符的前缀"AB"在母串中出现过吗?母串是"ABD",而前缀"AB"是母串的前两个字符,所以是的。所以,正确的结果是2,原代码返回2,正确。 因此,原代码的nizhao函数在这种情况下是正确的。 综上,原代码可能正确处理所有情况,因为每当模式的前缀k的节点被标记时,其路径上的所有节点也被标记,从而res的累加次数等于k,正确。这可能是因为在search函数中,处理母串时,每个字符的匹配路径会依次访问Trie中的路径节点,从而标记所有路径上的节点。因此,原代码中的nizhao函数的累加方式是正確的。</think>该程序解决的是玄武密码问题,要求找出多个查询字符串在母串中的最长前缀匹配。程序采用**AC自动机**算法高效处理多模式匹配问题,下面逐步解析其实现原理: --- ### **核心思路** 1. **构建Trie树**:将所有查询字符串插入字典树,形成多模式集合。 2. **构建失败指针**:通过BFS为每个节点建立失败指针(类似KMP的next数组),实现匹配失败时的快速跳转。 3. **母串匹配**:用母串在Trie树上扫描,标记所有被访问的节点。 4. **查询处理**:对每个查询字符串,沿Trie路径统计被标记的最长前缀长度。 --- ### **关键代码解析** #### 1. **字符映射** ```cpp int calc(char a) { if (a == 'N') return 0; else if (a == 'S') return 1; else if (a == 'W') return 2; else return 3; // E } ``` 将字符`N/S/W/E`映射为数字`0-3`,便于Trie子节点索引。 #### 2. **插入模式串到Trie** ```cpp void insert(char* a) { int now = root; for (int i = 0; a[i] != '\0'; i++) { int bit = calc(a[i]); if (!trie[now][bit]) trie[now][bit] = ++cnt; // 动态分配节点 now = trie[now][bit]; } } ``` 将每个模式串插入Trie树,构建基本结构。 #### 3. **构建失败指针** ```cpp void buildfail() { queue<int> line; for (int i = 0; i < 4; i++) { if (trie[root][i]) { fail[trie[root][i]] = root; line.push(trie[root][i]); } } while (!line.empty()) { int now = line.front(); line.pop(); for (int i = 0; i < 4; i++) { if (trie[now][i]) { fail[trie[now][i]] = trie[fail[now]][i]; // 核心:子节点失败指针 line.push(trie[now][i]); } else { trie[now][i] = trie[fail[now]][i]; // 路径压缩优化 } } } } ``` 通过BFS逐层构建失败指针,利用路径压缩优化转移路径。 #### 4. **母串匹配与标记** ```cpp void search() { int now = root; for (int i = 0; t[i] != '\0'; i++) { int index = calc(t[i]); now = trie[now][index]; int temp = now; while (temp != root) { // 沿失败链标记所有前缀 vis[temp] = true; temp = fail[temp]; } } } ``` 遍历母串,沿Trie树转移,并通过失败链标记所有可能的匹配前缀节点。 #### 5. **查询最大匹配长度** ```cpp int nizhao(char* a) { int now = root, res = 0; for (int i = 0; a[i] != '\0'; i++) { int bit = calc(a[i]); now = trie[now][bit]; if (vis[now]) res++; // 累加被标记的节点数 } return res; } ``` 沿查询字符串路径遍历Trie,统计被标记的节点数(即最长匹配长度)。 --- ### **算法正确性** - **标记覆盖性**:`search`函数通过失败链确保所有可能的前缀均被标记。 - **路径连续性**:由于Trie路径的唯一性,被标记的节点必然对应连续的前缀。 - **累加有效性**:查询路径中被标记的节点数即为最长前缀长度(每个被标记节点对应其长度的前缀在母串中存在)。 --- ### **复杂度分析** - **时间**:构建Trie和失败指针为$O(L)$(L为所有模式串总长),母串匹配为$O(N)$,查询为$O(M \cdot avg_len)$。 - **空间**:Trie节点数为$O(L \cdot 4)$,属于线性空间复杂度。 该程序通过AC自动机高效解决了多模式前缀匹配问题,适用于大规模数据场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值