蓝桥杯刷题 Day2 AC自动机(二次加强版)

server/2025/3/19 20:17:03/

蓝桥杯刷题 Day2 AC自动机(二次加强版)


文章目录

  • 蓝桥杯刷题 Day2 AC自动机(二次加强版)
  • 前言
  • 完整代码
  • 一、AC自动机(二次加强版)
    • 1. 解题思路
    • 1.1 问题抽象:
    • 1.2 解题步骤
    • 2. 拆解代码
      • 2.1结构体
      • 2.2 输入
      • 2.3 Trie树的创建
      • 2.4 构建失败指针(BFS层级遍历)
    • 2.5 匹配文本串
    • 2.6 拓扑排序
    • 2.6 输出
    • 3. 题后收获
      • 3.1 知识点


前言

今天写牛客网模板题中的字符串模块


完整代码

import java.util.*;// 定义结构体,用来构建树
class TrieNode{TrieNode[] children = new TrieNode[26]; // 子节点数组TrieNode fail; // 失败指针(类似于KMP中的next数组)int cnt = 0; // 统计子节点访问的次数List<Integer> ids = new ArrayList<>(); // 创建了一个动态数组,专门存储int类型,并通过list接口列表来引用
}public class Main {public static void main(String[] args){// 输入Scanner scanner = new Scanner(System.in);int n = scanner.nextInt(); // 输入模式串个数String[] patterns = new String[n]; // 输入模式串for(int i = 0; i < n; i++){patterns[i] = scanner.next();}String s = scanner.next(); // 输入文本串// 构建Tire树TrieNode root = new TrieNode(); // 创建根节点List<TrieNode> nodesOrder = new ArrayList<>(); // 记录所有节点的处理顺序(用于后续拓扑排序)TrieNode[] endNodes = new TrieNode[n]; // 记录每个模式串的结束节点for(int i = 0; i < n; i++){String p = patterns[i]; // 记录每个模式串TrieNode curr = root; // 记录节点// 将p转换成字符数组并遍历for(char c:p.toCharArray()){int idx = c - 'a'; // 将字符映射成ASCII值,便于记录// 若子节点不存在则创建if(curr.children[idx] == null){curr.children[idx] = new TrieNode();}curr = curr.children[idx];}curr.ids.add(i); // 将当前模式串索引添加到结束节点的ids列表中endNodes[i] = curr; // 保存结束节点?}// 构建失败指针(BFS层级遍历)/** 使用队列管理Trie树节点的处理顺序* 队列用于按层或按广度优先顺序(BFS)处理节点* Queue是一种数据结构,遵循先进先出(FIFO)的插入顺序* */// 接口                      具体实现Queue<TrieNode> queue = new LinkedList<>();root.fail = null;// 初始化根节点的子节点for (int i = 0; i < 26; i++) {if(root.children[i] != null){root.children[i].fail = root; // 失败指针指向根queue.add(root.children[i]);}}// isEmpty判断为空,poll取出队列头部节点while(!queue.isEmpty()){TrieNode curr = queue.poll();nodesOrder.add(curr); // 记录节点处理顺序// 遍历**当前节点**的所有子节点for (int i = 0; i < 26; i++) {TrieNode child = curr.children[i];if(child != null){TrieNode fail = curr.fail;// 若fail存在但fail没有对应字符i的子节点,则沿着失败指针链向上回溯。while(fail != null && fail.children[i] == null){fail = fail.fail;}// 否则,指向fail的字符i子节点,即最长后缀的末尾节点。child.fail = (fail == null) ? root : fail.children[i];queue.add(child); // 子节点入队}}}// 匹配文本串,统计cntTrieNode curr = root;for(char c:s.toCharArray()){int idx = c - 'a';while(curr != root && curr.children[idx] == null){curr = curr.fail;}if(curr.children[idx] != null){curr = curr.children[idx];}curr.cnt++;}// 拓扑排序优化(逆序累加)for (int i = nodesOrder.size() - 1; i >= 0 ; i--) {TrieNode node = nodesOrder.get(i);if(node.fail != null){// 将当前节点的cnt累加到失败指针节点的cnt中node.fail.cnt += node.cnt;}}// 输出for (int i = 0; i < n; i++) {System.out.println(endNodes[i].cnt);}}}

一、AC自动机(二次加强版)

原题地址: AC自动机(二次加强版)

1. 解题思路

1.1 问题抽象:

在一篇文章(文本串)中查找多个单词(模式串)对应的位置
核心思想:Trie树和失败指针

1.2 解题步骤

  1. Tire树构建:一种树形结构,用来存放多个单词
  2. 失败指针:查找失败后回退
  3. 文本匹配与统计:用文本串s在Trie树上走
  4. 拓扑排序:全局统计,从底向上统计

2. 拆解代码

2.1结构体

// 定义结构体,用来构建树
class TrieNode{TrieNode[] children = new TrieNode[26]; // 子节点数组TrieNode fail; // 失败指针(类似于KMP中的next数组)int cnt = 0; // 统计子节点访问的次数List<Integer> ids = new ArrayList<>(); // 创建了一个动态数组,专门存储int类型,并通过list接口列表来引用
}

2.2 输入

 // 输入Scanner scanner = new Scanner(System.in);int n = scanner.nextInt(); // 输入模式串个数String[] patterns = new String[n]; // 输入模式串for(int i = 0; i < n; i++){patterns[i] = scanner.next();}String s = scanner.next(); // 输入文本串

2.3 Trie树的创建

  1. 将模式串分为根节点和子节点,为了输出模式串T在文本串S中出现的次数,需要记录模式串的结束结点和节点的处理顺序
 // 构建Tire树TrieNode root = new TrieNode(); // 创建根节点List<TrieNode> nodesOrder = new ArrayList<>(); // 记录所有节点的处理顺序(用于后续拓扑排序)TrieNode[] endNodes = new TrieNode[n]; // 记录每个模式串的结束节点for(int i = 0; i < n; i++){String p = patterns[i]; // 记录每个模式串TrieNode curr = root; // 记录节点// 将p转换成字符数组并遍历for(char c:p.toCharArray()){int idx = c - 'a'; // 将字符映射成ASCII值,便于记录// 若子节点不存在则创建if(curr.children[idx] == null){curr.children[idx] = new TrieNode();}curr = curr.children[idx];}curr.ids.add(i); // 将当前模式串索引添加到结束节点的ids列表中endNodes[i] = curr; // 保存结束节点?}

2.4 构建失败指针(BFS层级遍历)

// 构建失败指针(BFS层级遍历)/** 使用队列管理Trie树节点的处理顺序* 队列用于按层或按广度优先顺序(BFS)处理节点* Queue是一种数据结构,遵循先进先出(FIFO)的插入顺序* */// 接口                      具体实现Queue<TrieNode> queue = new LinkedList<>();root.fail = null;// 初始化根节点的子节点for (int i = 0; i < 26; i++) {if(root.children[i] != null){root.children[i].fail = root; // 失败指针指向根queue.add(root.children[i]);}}// isEmpty判断为空,poll取出队列头部节点while(!queue.isEmpty()){TrieNode curr = queue.poll();nodesOrder.add(curr); // 记录节点处理顺序// 遍历**当前节点**的所有子节点for (int i = 0; i < 26; i++) {TrieNode child = curr.children[i];if(child != null){TrieNode fail = curr.fail;// 若fail存在但fail没有对应字符i的子节点,则沿着失败指针链向上回溯。while(fail != null && fail.children[i] == null){fail = fail.fail;}// 否则,指向fail的字符i子节点,即最长后缀的末尾节点。child.fail = (fail == null) ? root : fail.children[i];queue.add(child); // 子节点入队}}}

2.5 匹配文本串

// 匹配文本串,统计cntTrieNode curr = root;for(char c:s.toCharArray()){int idx = c - 'a';while(curr != root && curr.children[idx] == null){curr = curr.fail;}if(curr.children[idx] != null){curr = curr.children[idx];}curr.cnt++;}

2.6 拓扑排序

  // 拓扑排序优化(逆序累加)for (int i = nodesOrder.size() - 1; i >= 0 ; i--) {TrieNode node = nodesOrder.get(i);if(node.fail != null){// 将当前节点的cnt累加到失败指针节点的cnt中node.fail.cnt += node.cnt;}}

2.6 输出

        // 输出for (int i = 0; i < n; i++) {System.out.println(endNodes[i].cnt);}

3. 题后收获

3.1 知识点

  1. 创建一个列表,用于按顺序存储指定类型的对象:List nodesOrder = new ArrayList<>();
  2. 将p转换成字符数组并遍历:for(char c:p.toCharArray())
  3. 将当前模式串索引添加到结束节点的ids列表中:curr.ids.add(i)
  4. 将字符映射成ASCII值,便于记录:int idx = c - ‘a’
  5. 用于队列的数据结构:Queue queue = new LinkedList<>()
  6. isEmpty()判断为空,poll()取出队列头部节点

http://www.ppmy.cn/server/176336.html

相关文章

Deepseek r1 本地部署

1.环境需求&#xff1a; 1. 独显 2. 最好32g内存 3. 安装 ollama https://ollama.com/download/windows 2. 安装 deepseek-r1 https://ollama.com/library/deepseek-r1 根据显存选择对应大小的模型&#xff0c;推荐能将整个模型放入显存的大小&#xff0c;放…

桂链:什么是区块链策略?

桂链是基于Hyperledger Fabric开源区块链框架扩展开发的区块链存证平台&#xff0c;是桂云网络&#xff08;OSG&#xff09;公司旗下企业供应链、流程审批等场景数字存证软件产品&#xff0c;与桂花流程引擎&#xff08;Osmanthus&#xff09;并列为桂云网络旗下的标准与可定制…

游戏成瘾与学习动力激发策略研究——了解“情感解离”“创伤理论”

一、情感解离(Emotional Dissociation) 定义:情感解离是一种心理防御机制,指个体在经历无法承受的情绪压力或创伤时,通过切断情感体验与认知、记忆或现实感知的联系来保护自我。它不是简单的“麻木”,而是大脑为应对极端刺激而启动的“紧急逃生通道”。 核心特征 1、意…

windows上LISTENER监听器中显示“监听程序不支持服务”

现象&#xff1a; 在windows上安装完数据库后&#xff0c;检查监听的时候&#xff0c;出现如下的错误 问题排查&#xff1a; 既然监听能启动&#xff0c;说明在$ORACLE_HOME/network/admin/listener.ora文件配置的无问题&#xff0c;目前来看&#xff0c;是监听不支持服务。 …

【ES6】04-对象 + 类 + 模板字符串 + 解构 + 字符串

本文介绍对象类解构赋值字符串的操作和方法。 目录 1. 对象 1.1 创建对象 1.2 添加属性 1.3 删除 1.4 判断是否存在 1.5 属性名数量 1.6 for...in遍历 1.7 forEach遍历 1.8 清空对象 2. 类 2.1 创建对象和模板字符串 2.2 私有属性值 2.3 继承extends 3. 解构 3…

Groove 清除环境变量,以防应用程序因为环境变量设置了错误的 Qt 插件路径而启动失败

if "QT_QPA_PLATFORM_PLUGIN_PATH" in os.environ:os.environ.pop("QT_QPA_PLATFORM_PLUGIN_PATH") 这段代码的意思是检查 QT_QPA_PLATFORM_PLUGIN_PATH 是否在环境变量中&#xff0c;如果存在&#xff0c;就将其从环境变量中移除。 具体解释如下&#x…

Docker搭建MySQL主从服务器

一、在主机上创建MySQL配置文件——my.cnf master服务器配置文件路径&#xff1a;/data/docker/containers/mysql-cluster-master/conf.d/my.cnf slave服务器配置文件路径&#xff1a; /data/docker/containers/mysql-cluster-master/conf.d/my.cnf master服务配置文件内容 …

面试提问:数仓设计不分层可以吗?

目录 一、数据仓库分层的目的 二、不分层的适用场景 1. 小型团队或简单业务 2. 实时数据流处理 3. 探索性分析或临时需求 4. 现代架构的演进 三、不分层的潜在问题 1. 数据冗余与一致性 2. 维护复杂度 3. 性能瓶颈 4. 数据质量风险 四、折中方案&#xff1a;轻量级分…