1 条题解
-
0
题解:动态二进制串匹配与删除
问题分析
我们有一个二进制串 ,进行 次操作:
第 次操作:
- 将 转为二进制字符串 (无前导0)
- 在 中找到 第一次出现的位置,删除该子串
- 输出第一次出现的位置和出现次数
- 如果没找到,输出
-1 0
关键难点:
- 可达 ,不能直接暴力匹配
- 删除操作会改变字符串和所有子串位置
- 需要高效维护匹配信息
1. 关键观察
模式串长度很小:
是 的二进制表示,由于 ,所以 ()。
模式串最大长度只有 20!
受影响范围有限:
删除一个长度 的子串时,受影响的区域是:
- 删除区间本身
- 删除区间前后各 个字符(因为可能影响长度 20 的子串)
2. 算法框架
我们可以维护:
- 当前字符串的所有长度 ≤ 20 的子串的出现信息
- 用滚动哈希快速匹配
- 用平衡树/线段树维护字符串的删除状态
但由于 很大,我们需要更高效的方法。
3. 利用模式串长度小的特性
对于每个长度 (1 到 20),维护:
first_pos[L][hash]:哈希值为hash的长度为 的子串第一次出现位置count[L][hash]:出现次数- 用双哈希避免冲突
4. 数据结构设计
字符串表示:
用数组存储原字符串,用
deleted标记删除状态。实际字符串 = 所有
deleted[i] = false的字符按顺序拼接。查找第一个有效匹配:
从
first_pos中找到的位置可能已被删除,需要向后查找第一个未被删除的出现。维护子串信息:
预处理所有长度 ≤ 20 的子串信息,删除时局部更新。
5. 具体实现步骤
预处理:
// 计算所有长度1~20子串的哈希值和出现信息 for (int L = 1; L <= 20; L++) { for (int i = 1; i + L - 1 <= n; i++) { hash_val = compute_hash(s, i, i+L-1); if (!first_pos[L].count(hash_val)) { first_pos[L][hash_val] = i; } count[L][hash_val]++; } }删除操作:
删除区间 后:
- 标记
deleted[l..r] = true - 更新受影响的子串信息:
- 受影响范围:
- 对这个范围内的所有长度 ≤ 20 的子串重新计算哈希和统计信息
6. 优化查找第一个有效匹配
由于删除操作,
first_pos中记录的位置可能已失效。我们需要一个方法快速找到第一个未被删除的匹配。
方法:对每个哈希值,维护一个有效位置的有序集合
- 用
set或平衡树存储所有出现位置 - 删除时从集合中移除对应位置
- 查找时取集合的最小值
但这样空间开销大,需要进一步优化。
7. 更实用的方法:KMP + 链表
我们可以用双向链表维护剩余字符串:
struct Node { int pos; // 在原串中的位置 char val; // 字符值 Node *prev, *next; }; // 初始化链表 Node* head = build_linked_list(s);查找时:
- 从链表头开始,用 KMP 算法查找模式串
- 找到第一个匹配后,从链表中删除对应节点
- 输出位置和通过 KMP 统计的出现次数
但 KMP 每次 太慢,需要优化。
8. 最终方案:滚动窗口 + 局部更新
由于模式串长度 ≤ 20,我们可以在滑动窗口中实时维护匹配信息。
维护一个当前有效字符串的哈希索引:
- 用滑动窗口计算所有长度 ≤ 20 子串的哈希
- 删除时只更新受影响窗口内的哈希
具体步骤:
- 初始化:计算初始字符串所有长度 ≤ 20 子串的哈希,存入哈希表
- 查找:在哈希表中查找 的哈希,找到第一个有效位置
- 删除:
- 从哈希表中移除被删除子串影响的所有哈希
- 标记删除区间
- 统计:在删除前统计所有出现次数
9. 查找第一个有效位置的优化
对每个哈希值,我们维护一个位置的最小堆(优先队列):
unordered_map<HashValue, priority_queue<int, vector<int>, greater<int>>> pos_map;查找时:
while (!pos_map[target_hash].empty()) { int pos = pos_map[target_hash].top(); if (!deleted[pos]) { // 找到第一个有效位置 return pos; } pos_map[target_hash].pop(); // 移除无效位置 } return -1; // 未找到
10. 统计出现次数
删除前需要统计 在当前字符串中的出现次数。
由于模式串短,我们可以:
- 在删除区间附近扫描统计
- 或者维护每个哈希值的计数器,删除时更新
但计数器需要随删除操作动态更新。
11. 完整算法
#include <iostream> #include <string> #include <vector> #include <unordered_map> #include <queue> #include <cmath> using namespace std; const int MAXN = 1000005; int n; string s; bool deleted[MAXN]; // 哈希结构 struct Hash { long long h1, h2; bool operator==(const Hash& other) const { return h1 == other.h1 && h2 == other.h2; } }; namespace std { template<> struct hash<Hash> { size_t operator()(const Hash& h) const { return h.h1 * 13131 + h.h2; } }; } // 维护每个长度的子串信息 unordered_map<Hash, priority_queue<int, vector<int>, greater<int>>> first_pos[21]; unordered_map<Hash, int> count[21]; // 计算子串哈希 Hash compute_hash(int l, int r) { // 实现双哈希 } // 获取数字的二进制表示 string to_binary(int x) { string res; while (x) { res = char('0' + (x & 1)) + res; x >>= 1; } return res; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n >> s; s = " " + s; // 1-indexed // 预处理所有子串 for (int L = 1; L <= 20; L++) { for (int i = 1; i + L - 1 <= n; i++) { Hash h = compute_hash(i, i+L-1); first_pos[L][h].push(i); count[L][h]++; } } for (int i = 1; i <= n; i++) { string t = to_binary(i); int len = t.length(); // 计算t的哈希 Hash target_hash = compute_hash_from_string(t); // 查找第一个有效位置 int first_occurrence = -1; while (!first_pos[len][target_hash].empty()) { int pos = first_pos[len][target_hash].top(); if (!deleted[pos]) { first_occurrence = pos; break; } first_pos[len][target_hash].pop(); } if (first_occurrence == -1) { cout << "-1 0\n"; continue; } // 统计出现次数(需要在删除前统计) int occurrence_count = 0; // 这里需要实现统计逻辑 cout << first_occurrence << " " << occurrence_count << "\n"; // 删除子串 for (int j = first_occurrence; j < first_occurrence + len; j++) { deleted[j] = true; } // 更新受影响的子串信息 update_affected(first_occurrence, first_occurrence + len - 1); } return 0; }
12. 复杂度分析
- 预处理:
- 每次操作:
- 查找: 平均
- 删除更新:
- 总复杂度:,在 时勉强可行
13. 进一步优化
对于大数据,需要更精细的实现:
- 用基数排序替代哈希表
- 用位运算加速哈希计算
- 批量处理删除更新
总结
本题的关键在于:
- 利用模式串长度 ≤ 20 的特性
- 维护所有短子串的哈希索引
- 高效处理删除操作的影响范围
- 快速查找第一个有效匹配位置
这是一个字符串哈希 + 数据结构的综合问题。
- 1
信息
- ID
- 5568
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者