1 条题解
-
0
ROI 2019 Day2 T4. 寻找思路 题解
问题描述
给定模式串 (长度 )和经过压缩的文本串 ,求 在解压后的 中出现的次数。
压缩格式由 个块组成:1 w:将字符串 追加到 末尾2 pos len:从 的 位置开始,连续复制 个字符到末尾
限制条件:
- (解压后文本可能极大)
核心难点
- 文本可能极大: 可达 ,无法显式展开
- 复制操作可能交叉:复制区间 可能包含正在生成的部分
- 高效计数:需要在 或类似复杂度内完成
关键观察
1. 模式串匹配的特征
设模式串 的长度为 。当文本 扩展时:
- 新的匹配可能出现在:
- 新添加的 内部
- 复制区域内部
- 跨越新旧文本的边界
2. 复制操作的数学性质
第 个操作后, 的长度为 。 对于复制操作
2 pos len:- 相当于从 中取子串
- 若 ,则是纯复制(不重叠)
- 否则可能产生重叠复制,形成类周期结构
3. KMP/自动机方法
可使用 KMP 或 AC 自动机跟踪匹配状态:
- 在扩展文本时,维护当前后缀与模式串的匹配长度
- 每次添加字符时更新状态
- 但需要处理批量复制操作的高效更新
算法设计
总体思路:状态转移 + 倍增
-
预处理:
- 对模式串 构建 KMP 的 数组(失配函数)
- 构建转移自动机:
next_state[state][c]表示当前匹配长度为 ,遇到字符 后的新匹配长度
-
操作处理:
- 对每个操作,不显式生成文本,而是计算:
- 操作后文本的总长度
- 从初始空状态(匹配长度 0)处理该操作后得到的结束状态
- 该操作内部产生的匹配次数
- 对每个操作,不显式生成文本,而是计算:
-
复制操作的高效处理: 对于
2 pos len:- 如果复制区间不重叠且长度不太大,可逐字符模拟(使用转移自动机)
- 对于大长度,利用倍增/矩阵幂加速:
- 定义 :从位置 开始复制 个字符后,状态的转移结果和产生的匹配数
- 通过二进制分解 快速计算
-
边界匹配: 跨操作边界的匹配:
- 记录每个操作结束时的 KMP 状态(匹配长度)
- 当下一个操作开始时,继续从该状态匹配
数据结构
struct BlockResult { long long new_len; // 操作后文本长度 int end_state; // 操作结束时的KMP状态 long long match_count; // 本操作内部新增的匹配数 vector<int> prefix_state; // 文本前缀的状态(用于查询区间) };算法步骤
- 读取模式串,构建 KMP 自动机
- 初始化:当前文本 为空,状态为 0,总匹配数为 0
- 依次处理每个操作:
- 类型 1:添加字符串 ,逐字符更新状态
- 类型 2:复制操作,使用倍增方法处理
- 输出总匹配数
倍增方法详解
对于复制操作
2 pos len:预处理倍增表
令 表示:从 KMP 状态 开始,处理从某个固定位置开始的 个字符后的结果。
但由于复制起点 可能不同,我们需要按位置查询字符。
替代方案:在线段树上预处理
-
构建文本的"虚拟"线段树,每个节点存储:
- 该区间对应的字符串
- 从各个初始状态处理该区间后的结果
-
对于复制区间 :
- 在线段树中分解为 个区间
- 按顺序处理这些区间,传递状态
实际实现中的简化
考虑到 ,实际展开的"基础文本"并不大。
我们可以:- 维护前 个字符(或 个字符,足够处理边界)
- 对于超长的复制,利用周期性:
- 如果复制区间完全在已展开文本内,直接复制
- 否则,重复复制直到填满需求或发现周期
复杂度分析
- KMP 预处理: 或
- 类型 1 操作:总
- 类型 2 操作:
- 短复制:
- 长复制:利用周期性,
- 总复杂度:
C++ 实现框架
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXM = 200005; const int MAXL = 1000005; // 维护的文本最大长度 int m, n; string pattern; int pi[MAXM]; int transition[MAXM][26]; // 自动机转移 struct Operation { int type; string s; // type=1时使用 ll pos, len; // type=2时使用 }; // 维护一段实际文本 string maintained_text; ll total_len = 0; ll total_matches = 0; int current_state = 0; // KMP预处理 void build_kmp(const string& p) { pi[0] = 0; for (int i = 1; i < m; i++) { int j = pi[i-1]; while (j > 0 && p[i] != p[j]) j = pi[j-1]; if (p[i] == p[j]) j++; pi[i] = j; } // 构建转移表 for (int state = 0; state <= m; state++) { for (char c = 'a'; c <= 'z'; c++) { if (state < m && c == pattern[state]) { transition[state][c-'a'] = state + 1; } else if (state > 0) { transition[state][c-'a'] = transition[pi[state-1]][c-'a']; } else { transition[state][c-'a'] = 0; } } } } // 处理一个字符,更新状态和匹配数 void process_char(char c) { current_state = transition[current_state][c-'a']; if (current_state == m) { total_matches++; current_state = pi[m-1]; // 可重叠匹配 } } // 处理类型1操作 void process_type1(const string& s) { for (char c : s) { maintained_text.push_back(c); if (maintained_text.size() > MAXL) { maintained_text.erase(maintained_text.begin()); } process_char(c); } total_len += s.length(); } // 处理类型2操作 void process_type2(ll pos, ll len) { // 确保pos在有效范围内 pos--; // 转换为0-based // 策略:如果复制的全部内容都在maintained_text中,直接复制 // 否则,分段处理 ll copied = 0; while (copied < len) { // 计算本次能复制的长度 ll available = min(len - copied, total_len - pos); // 如果当前位置在维护的文本中 if (pos < maintained_text.size()) { ll start_idx = pos; for (ll i = 0; i < available; i++) { char c = maintained_text[(start_idx + i) % maintained_text.size()]; process_char(c); maintained_text.push_back(c); if (maintained_text.size() > MAXL) { maintained_text.erase(maintained_text.begin()); } } } else { // 需要模拟复制 // 简化:直接扩展maintained_text // 实际应使用更高效的方法 for (ll i = 0; i < available; i++) { // 这里简化处理,实际需要从虚拟文本获取字符 // 使用周期性:文本可能由之前的复制生成 process_char('a'); // 占位 } } copied += available; pos = 0; // 后续从文本开头复制 } total_len += len; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> m >> n; cin >> pattern; build_kmp(pattern); vector<Operation> ops(n); for (int i = 0; i < n; i++) { int type; cin >> type; if (type == 1) { string s; cin >> s; ops[i] = {type, s, 0, 0}; } else { ll pos, len; cin >> pos >> len; ops[i] = {type, "", pos, len}; } } // 处理操作 for (int i = 0; i < n; i++) { if (ops[i].type == 1) { process_type1(ops[i].s); } else { process_type2(ops[i].pos, ops[i].len); } } cout << total_matches << endl; return 0; }注意事项
- 大整数处理:使用
long long,注意 - 内存管理:只维护必要的文本前缀
- 边界情况:
- 模式串长度为 1
- 复制操作产生重叠
- 文本全为同一字符
- 取模:本题不需要取模,直接输出匹配数
进一步优化
对于大数据,需要实现完整的倍增方法:
- 构建块级别的转移:将每个操作视为一个"块",预处理从任意状态处理该块后的结果
- 倍增处理长复制:对于复制操作,使用快速幂思想组合转移
- 查询优化:使用前缀和记录文本字符,支持 查询任意位置的字符
总结
本题是字符串匹配与动态文本生成的结合,主要考察:
- KMP/自动机的应用
- 对压缩格式的高效处理
- 倍增思想在字符串操作中的应用
- 大数据的处理能力
关键突破点在于将复制操作视为状态转移,并使用倍增加速,避免显式展开极大文本。
- 1
信息
- ID
- 5795
- 时间
- 5000ms
- 内存
- 1024MiB
- 难度
- 10
- 标签
- 递交数
- 5
- 已通过
- 1
- 上传者