简单字符串相关
有关字符串
KMP
记录主串的当前位 i ,所在的后缀与模式串的最大匹配前缀。
f[i]:最大的k,使得A的子串(1..i)的后k位等于B的前k位。
next[i]:最大的k(k< i),使得B的(1..i)的子串中前k位等于后k位。
如果A[i + 1]匹配上了B[f[i] + 1], f[i + 1] = f[i] + 1;
如果没有匹配上,k = nxt[k],不断把指针向前跳直到 k = 0 或者匹配成功。
具体实现就是先 B 串自己和自己匹配,然后用 B 去匹配 A 。
代码
string A, B;
int nxt[maxn];
void getnxt(){
int k = 0;
for(int i = 2; i <= B.size(); ++i){
while(k > 0 && B[k + 1] != B[i]) k = nxt[k];
if(B[k + 1] == B[i]) k++;
nxt[i] = k;
}
}
void KMP(){
int k = 0;
for(int i = 1; i <= A.size(); ++i){
while(k > 0 && A[i] != B[k + 1]) k = nxt[k];
if(B[k + 1] == A[i]) k++;
if(k == A.size()){
tot++;
}
}
}
int main(){
cin >> (B + 1);
cin >> (A + 1);
getnxt();
KMP();
return 0;
}
AC 自动机
Manacher 马拉车算法
用于求一个字符串中最长回文串。时间复杂度是线性的。
其基本思想是一个回文串里如果有一个回文子串,那么相对称的地方就会有另一个一摸一样的回文子串,而我们就可以用较为靠前的那个回文子串去简化较为靠后的回文子串的扩展。
题外话:如果字符集不仅仅是英文字母而扩大到几百几千几万,那对于程序运行时间的影响是什么?
奇偶长度回文串的统一处理
我们把原字符串的相邻字符之间加上分隔符 #
, 这样奇偶长度的串在代码细节上就没有区别了。
如:ABA => A#B#A 回文串以B为对称中心,短半径为2;
ABBA => A#B#B#A 回文串以#为中心,短半径为3;
这样处理之后所有的回文串长度都变成了奇数。
基本变量
字符串s,经添加分隔符#处理。n 为添加完#后的总长度。
全局维护已找到的靠右的回文串的边界 l 和 r。即在所有已找到的回文串(li, ri)中,r是最大的ri。注意回文串(l,r)并不一定是已找到的最长的回文串,即在所有的ri - li + 1中, r - l + 1 并不一定是最大的。
用d[i]来表示以s[i]为对称中心的最长回文串的短半径。所谓短半径即为整个回文串去掉对称中心后长度除以2。
朴素算法
for(int i = 1; i <= n; ++i){
int j = 0;//短半径
while(i - j >= 1 && i + j <= n && s[i - j] == s[i + j]){
j++;
}
d[i] = j - 1;
}
运行过程
假设我们已经求完了d[1...i - 1],并且维护了 l 和 r ,现在想办法求解d[i]。
此时有两种情况发生: $ (1) i > r ; (2) i \leq r ;$
当 \(i > r\) 时,我们维护的回文串(l, r) 不会对 d[i]的求解造成任何的帮助,此时采用朴素算法迭代d[i]求解。在求出 d[i] 后更新(l, r)。
当 $i \leq r $ 时, 能够确定的是 \((l+r)/2 < i \leq r\)(因为(l, r)的来源肯定是以mid为对称中心,而mid肯定小于i), 所以我们取 i 关于 mid 的对称点 j(\(j = l + r - i\)), d[j]是已知的。由于 i 和 j 的对称关系, 以 i 为中心起码有短半径为 d[j]的回文串。当 \(j - d[j] \geq l\) 时,以 j 为中心的回文串不会超出 l 的左边, 相应的以 i 为中心的回文串不会超出 r 的右边,\(d[i] = d[j]\)。而当 \(j - d[j] < l\) 时, 以 i 为中心的回文串会扩展到 r 的右边,扩展出去的部分就需要朴素算法求解了。最后记得在每次得到 d[i] 之后更新(l, r)的值。
复杂度
算法的复杂度是朴素算法进行的次数,而每次朴素算法迭代一次必然对应着 r 的更新。 r 的更新是线性的, 故这个算法的复杂度是线性的。
修缮'#'
需要注意的是,‘#’的田间会为我们的程序带来一些问题,比如:
A#B#A 明明是一个长度为5的回文串,却可能被当作 #A#B#A# 而长度变为7;
A#B#B#A 本来是一个长度为7的回文串,却可能被当作 #A#B#A#B# 而短半径被当作4;
而在类似于 C#C, A#C#A等以非分隔符作为左右边界的回文串中,就不存在这样的问题。
所以我们只需稍作特判,如果边界字符是分隔符,将短半径减一即可。
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<ctime>
#include<vector>
#include<cstdio>
#include<string>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
using namespace std;
int rd(){
int res = 0, fl = 1;
char c = getchar();
while(!isdigit(c)){
if(c == '-') fl = -1;
c = getchar();
}
while(isdigit(c)){
res = (res << 3) + (res << 1) + c - '0';
c = getchar();
}
return res * fl;
}
char c, s[30000000];
string in;
int d[30000000], l, r, n, ans;
int main(){
cin >> in;
for(int i = 0; i < in.size(); ++i){
s[++n] = in[i];
s[++n] = '#';
}
n--;
l = 1; r = 0;
for(int i = 1; i <= n; ++i){
if(i > r){
int j = 1;
while(i - j >= 1 && i + j <= n && s[i - j] == s[i + j]){
j++;
}
d[i] = j - 1;
}
else{
int j = l + r - i;
j = min(d[j], j - l) + 1;
while(i - j >= 1 && i + j <= n && s[i - j] == s[i + j]){
j++;
}
d[i] = j - 1;
}
if(d[i] + i >= r){
r = i + d[i];
l = i - d[i];
}
if(i + d[i] >= 1 && i + d[i] <= n && s[i + d[i]] == '#') d[i]--;
ans = max(ans, d[i]);
}
printf("%d\n", ans + 1);
return 0;
}
有关fread
黑科技。很快很快。
fread(s + 1, 1, n, stdin);
s + 1 是首地址;
1 是每次读取的字节数,一个char是1个字节,空格也会读入,换行也会读入!
n 是读取次数;
神奇的是fread(s + 1, 1, 100, stdin)
和fread(s + 1, 2, 50, stdin)
和fread(s + 1, 25, 4, stdin)
是等价的。
有关 int
类型的读入
#ifndef DONLINE_JUDGE
char xch, xB[1 << 15], *xS = xB, *xTT = xB;
#define getc() (xS == xTT && (xTT = (xS = xB) + fread(xB, 1, 1 << 15, stdin), xS == xTT) ? 0 : *xS++)
#endif
#ifndef DBUFFETT
#define getc() getchar()
#endif
洛谷评测会使用 DONLINE_JUDEGE
, 本地使用的是DBUFFETT
, 编译选项里要加上-DBUFFETT
。
本文来自博客园,作者:咕咕坤,转载请注明原文链接:https://www.cnblogs.com/GuguKun/p/14969896.html