当前位置: 首页 > news >正文

前缀函数的运用

前缀函数的运用

KMP

在字符串中查找子串,本质是对前缀函数的运用。

例题1

给定一个文本串 ttt,和一个模板串 sss,找出 sssttt 中出现的所有位置。

题解

构造一个字符串 h=s+#+th=s+\#+th=s+#+thhh三个部分 组成,第一个部分是模板串 sss,第二个部分是一个在 sssttt 中都不会出现 的字符 #\##,第三部分是文本串 ttt

可以对 hhh 跑一遍前缀函数,然后所有前缀函数大小为 s.size() 的位置就是 sss 完整在文本串中出现且 sss最后一个字符 所处的位置。

由于 hhh 有偏移,所以得到的位置应该减去 2*s.size()-1

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int N = 1e6 + 7;struct PrifixFunction {int n;string s;vector <int> p;PrifixFunction (int _n, string _s) : s(_s), n(_n), p(_n + 1){}void getPrifixFunction () {p[0] = 0;for (int i = 1; i < n; i++) {int j = p[i - 1];while (j && s[j] != s[i]) {j = p[j - 1];}if (s[j] == s[i]) j ++;p[i] = j;}}
};void solve () {string t, s;cin >> t >> s;string h = s + "#" + t;PrifixFunction pp(h.size(), h);pp.getPrifixFunction();vector <int> ans;for (int i = 0; i < h.size(); i++) {if (pp.p[i] == s.size()) ans.push_back(i - 2*s.size() + 1);}for (auto i : ans) cout << i << endl;
}
signed main() {solve();
}

字符串的周期

对于字符串 sss,若 sss 存在周期 T(1≤T≤∣s∣)T(1\le T\le |s|)T(1Ts),则对于所有的 i∈[0,∣s∣−T−1]i\in[0,|s|-T-1]i[0,sT1] 都有 s[i]=s[i+T]s[i]=s[i+T]s[i]=s[i+T]

不难发现,若存在一个子串 rrr,既是 sss 的一个 真前缀,又是 sss 的一个 真后缀,那么 sss 一定有周期 t=∣s∣−∣r∣t= |s|-|r|t=sr

因为 i+t≤∣s∣−1i+t\le|s|-1i+ts1,当 iii最大值 的时候,该不等式取等号,而 iii 至多只能到 ∣r∣−1|r|-1r1,所以 t=∣s∣−∣r∣t=|s|-|r|t=sr

又因为这样的 rrr 的长度最大不超过 π(∣s∣−1)\pi(|s|-1)π(s1),所以我们就说 sss 的最小周期是 π(∣s∣−1)\pi(|s|-1)π(s1)

统计每个前缀的出现次数

例题1

给定一个长度为 nnn 的字符串 sss,统计 sss 的每个前缀在 sss 中的出现次数。

题解

若存在一个子串 rrr,既是 sss 的一个 真前缀,又是 sss 的一个 真后缀,那么我们就称 rrrsss 的一个 borderborderborder

xxxsss 的真前缀,如果 xxxsss 中出现了一次以上,那么必然能截取出一个子串 ttt,使得 xxxtttborderborderborder

所以我们对于每个子串 s[0...i]s[0...i]s[0...i],求出其所有的 borderborderborder 并进行累加即可。

如果一个长度为 lenlenlenborderborderborder 出现了 xxx 次,那么不管子串多长 p[len−1]p[len-1]p[len1] 都是仅次于 lenlenlenborderborderborder

所以一个长度为 lenlenlenborderborderborder 的出现次数,也代表了一部分 p[len−1]p[len-1]p[len1] 的贡献。

我们先记录每个子串最大长度的 borderborderborder 出现次数,然后倒序累加就行了。

随后别忘记加上前缀自身出现的一次。

vector<int> p(n);
vector<int> cnt(n + 1, 0); // cnt[i] 表示长度为 i 的前缀出现次数// 1. 计算前缀函数
for (int i = 1; i < n; i++) {int j = p[i - 1];while (j && s[j] != s[i]) j = p[j - 1];if (s[j] == s[i]) j++;p[i] = j;
}// 2. 统计每个长度的出现次数(不含自己作为前缀的那一次)
for (int i = 0; i < n; i++) cnt[p[i]]++;// 3. 把出现次数沿着 border 链上传递
for (int len = n; len > 0; len--) {cnt[p[len - 1]] += cnt[len];
}// 4. 每个前缀本身出现一次
for (int i = 1; i <= n; i++) cnt[i]++;

例题2

sss 中的每个前缀在 ttt 中出现的次数。

题解

构造一个字符串 h=s+#+th=s+\#+th=s+#+t,其中 #\## 是不在 sssttt 中存在的字符。

接下来我们就用例题 111 的求法,求出 hhh 的每个前缀在自身中出现的次数。

然后我们再对 sss 这个字符串单独计算一遍其每个前缀在自身中出现的次数。

将两个数组相减即可。

当我们需要用的时候,将它们封装到结构体里就行了。

sss 中本质不同子串的数量

例题1

sss 中本质不同子串的数量。

题解

考虑迭代的做法,设 kkk 是当前的 sss 中本质不同的子串的数量,若在 sss 后面增加一个字符 ccc,记作 s1s_1s1

那么最多增加 ∣s∣+1|s|+1s+1 个本质不同的子串,且这些子串都是以 ccc 结尾的。

此时我们可以将当前字符串 s1s_1s1 反转,设为 s1Ts_{1}^{T}s1T,对其跑一遍前缀函数。

然后求出 s1Ts_1^{T}s1T 的每个前缀在自身中出现多少次,那些只出现一次的就是增加的本质不同的子串。

求出 s1Ts_1^{T}s1T 中只出现一次的前缀,就相当于找出最大的 π\piπ 值,最大的 π\piπ 值表示前缀的字符串到哪里开始不重复出现。

所以只出现一次的前缀数量就相当于 ∣s∣+1−πmax|s|+1-\pi_{max}s+1πmax

http://www.dtcms.com/a/329769.html

相关文章:

  • Harmony OS 开发入门 第三章
  • Python Day29 CSS样式
  • Protobuf学习(1)—— 初识与安装
  • 代理解决跨域
  • SparseArray ArrayMap
  • Activity和Fragment生命周期
  • Spring进阶(八股篇)
  • 栈和队列详解
  • LeetCode刷题记录----437.路径总和Ⅲ(medium)
  • 学习:JS进阶[10]内置构造函数
  • HunyuanVideo-Avatar:为多个角色制作高保真音频驱动的人体动画
  • C++哈希进阶-位图
  • 计算机网络技术-知识篇(Day.1)
  • java14学习笔记-打包工具 (Incubator)
  • MoonBit Perals Vol.05: 函数式里的依赖注入:Reader Monad
  • JPrint免费的Web静默打印控件:PDF打印中文乱码异常解决方案
  • 什么是JSP和Servlet以及二者的关系
  • window显示驱动开发—多平面覆盖 VidPN 呈现
  • MVCC底层实现原理
  • Flask入门:从零搭建Web服务器
  • 雅思大作文笔记
  • iOS 签名证书在版本迭代和iOS上架中的全流程应用
  • Docker 在 Linux 中的额外资源占用分析
  • 智汇河套,量子“风暴”:量子科技未来产业发展论坛深度研讨加速产业成果转化
  • 信息学奥林匹克竞赛(NOI/NOIP/CSP) 学习进度自查表
  • 使用 Python Selenium 和 Requests 实现歌曲网站批量下载实战
  • 嵌入式学习 day50 IMX6ULL裸机开发 - 时钟
  • Linux 5.15.189-rt87 实时内核安装 NVIDIA 显卡驱动
  • 手机充电线贴标机如何使用
  • 内存可见性和伪共享问题