- B1: 前缀和与差分
- B2: 快速组合
- B3: 二分查找
- B4: 高精度加法
- B4: 高精度除法
- B4: 高精度乘法
- B4: 高精度减法
- D1: 单调栈
- D2: 单调队列
- D3: Spare Table
- D4: 字典树
- D5: 并查集
- D6: 树状数组-限定区间计数
- D6: 树状数组-单点更新区间求和
- D7: 线段树-基础
- G1: 二分图-判定
- G2: 二分图-最大匹配
- G3: Astar k短路 on matrix
- G4: 拓扑排序
- G5: 连通无环无向图的重心
- G6: MST-Kruskal
- G7: MST-Prim
- G8: ShortestPath-BellmanFord
- G9: ShortestPath-Dijkstra
- G9: ShortestPath-Dijkstra Heap OPT
- G10: ShortestPath-SPFA
- G11: ShortestPath-Floyd
- M1: 快速幂与龟速乘
- M2: 欧几里得-最大公约数
- M2: EX欧几里得-翡蜀定理
- M2: EX欧几里得-线性同余方程
- M2: EX欧几里得-乘法逆元
- M3: 费马小定理-乘法逆元
- M4: 分解质因数
- M4: 分解质因数-欧拉函数
- M4: 分解质因数-多数乘积约数计数
- M4: 分解质因数-多数乘积约数求和
- M5: 欧拉筛
- M5: 欧拉筛-质数筛
- M6: 埃氏筛-质数筛
- M7: Stein算法-最大公约数
- M8: 矩阵乘法与快速幂
- O1: 快速排序
- O2: 并归排序
- S1: KMP
- S2: 字符串哈希
class KMP {
const string s;
int ar[maxn + 1] {};
inline void prefunc() {
ar[0] = -1;
int i = 0, j = -1;
while (i < s.length()) {
if (j == -1 || s[i] == s[j]) ar[++i] = ++j;
else j = ar[j];
}
}
inline void ntfunc() {
for (int i = 0; i < s.length(); i++) {
if (ar[i] != -1 && s[i] == s[ar[i]]) ar[i] = ar[ar[i]];
}
}
public:
explicit KMP(string &p): s(p) { prefunc(), ntfunc(); }
int solve(string &tar) {
int ret = 0, i = 0, j = 0;
while (i < tar.length()) {
if (j == -1 || tar[i] == s[j]) {
++i, ++j;
if (j == s.length()) ++ret, j = ar[j];
} else j = ar[j];
}
return ret;
}
};
Simple Explanation
-
maxn是pattern的最大长度,prefix的长度是pattern.length()+1,nt的长度是pattern.length() - 注意构造
prefix数组和求解kmp主体函数中j指针的起始值不同 -
prefix[r]表示下标[0, r)序列两端真子串的最长同构长度,prefix[0]表示的序列为空,值为-1 - 如果用
prefix数组代替nt, 当p[j]!=t[i], 则不匹配, 将[0, j)左端的同构字串移动到右端继续匹配, 是kmp的主体思想 -
nt是对prefix的优化, 当[0, j)左端的同构字串的下一个字符与p[j]相同, 则一定不匹配, 即把多次移动优化为一次移动 - 对于题目的具体要求不同, 适当修改
count_patterns函数即可