浅谈字符串操作
这波乱搞
新年发博客?
不存在,只是开个坑
所以这篇文章干屁用
是
好吧,我基本是深夜更新
话不多说,我们就进入鬼畜奇妙的字符串世界吧
1.HASH,哈希
此算法可能是最简单的了
我们就时将每个字符串看成
不过这个
下面给出代码
const ull base = 233;
const ull mod = 1e9 + 5;
inline ull hash(char s[]) {
int len = strlen(s);
ull sum = 0;
for (int i = 0; i < len; i ++)
sum = (sum * base + (ull)(s[i])) % mod;
return sum;
}
接下来我门来看看
其实是模板题
只要算出每一个字符串的哈希值,然后从小到大排序,比较即可
注意,对于孪生素数,这里的数据卡掉了
代码如下
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ull unsigned long long
const int N = 1e5 + 7;
const ull base = 233;
const ull mod = 1e9 + 5;
using namespace std;
ull a[N];
char s[N];
int n, ans = 1;
inline ull hash(char s[]) {
int len = strlen(s);
ull sum = 0;
for (int i = 0; i < len; i ++)
sum = (sum * base + (ull)(s[i])) % mod;
return sum;
}
inline bool cmp(int x, int y) {
return x < y;
}
int main() {
cin >>n;
for (int i = 1; i <= n; i ++) {
scanf("%s", s);
a[i] = hash(s);
}
sort(a + 1, a + 1 + n, cmp);
for (int i = 1; i < n; i ++) {
if (a[i] != a[i + 1])
ans ++;
}
cout << ans << endl;
return 0;
}
例题的话,这里先给几道,有机会再更
咋求呢?
我们可以自我匹配,当这个
那么匹配不上的话,我们就将当前的
当然,我们每次算完肯定要记录啊,所以在每次算完后
然后我们贴贴代码
inline void pre() {
int j = 0;
for (int i = 2; i <= lb; i ++) {//第一位肯定是0,无需匹配
while (j && b[j + 1] != b[i])//匹配不上的时候
j = nxt[j];//就往回搜
if (b[j + 1] == b[i])
j ++;//不然往前搞
nxt[i] = j; //记录
}
}
习惯性手动操作一波
B:ababd
1.j = 0, i = 2
那么b[j + 1] != b[i] 并且 j == 0
所以nxt[i] = nxt[2] = j = 0
2.j = 0, i = 3
那么b[j + 1] == b[i] == 'a'
所以j ++
即nxt[i] = nxt[3] = j = 1
3.j = 1, i = 4
那么b[j + 1] == b[i]
所以 j ++
即nxt[i] = nxt[4] = j = 2
4.j = 2, i = 5
那么 b[j + 1] != b[i] 并且 j != 0
所以j = nxt[j] = nxt[2] = 0
那么这是b[j + 1] != b[i],但是 j == 0
所以nxt[i] = nxt[5] = j = 0
很简单易懂吧
所以我们就解决了
接下来就是正式的 什么,现在才到kmp,滚)
如果不行,就是
那么当我们匹配完毕,就是
同样,为了继续匹配,我们每次匹配完毕时,应该吧
注意,不是 每次操作 完,是 每次匹配完毕
贴下代码
inline void kmp() {
int j = 0;
for (int i = 1; i <= la; i ++) {//这个要从第一个字母匹配
while (j && b[j + 1] != a[i])//不匹配
j = nxt[j];//往回搜
if (b[j + 1] == a[i])
j ++;//不然往前搞
if (j == lb) {//匹配完毕
ans ++;
j = nxt[j];//为下次匹配做好准备
}
}
}
样例懒得模拟了,反正这个就和求
然后推荐例题,有空更新
KMP模板
关于KMP的巧妙运用 link
关于KMP的巧妙运用 link
3.Trie树
关于踹树
好吧我们进入正题
先来一个背景:AC自动机模板(easy)
上面说了
很明显,由图可知,根节点所有的孩子的
那么我们是不是可以这样:
把所有根节点的儿子的
然后接下来开始匹配
我们先遍历到这个
明显的,他没有一个是能够匹配的上的,于是他的
绿色的即为第三层的
接下来我们遍历
那么明显的有一个
那我们再来遍历
那么我们同样的还能找到那个
再来看
没有与之匹配的
便只能连向根节点
所以然后接下来便可以遍历
明显,
再来看
我们从
所以还是连向根节点
那么这到底是什么情况呢?
偶然?
我们看看之前所有连接的
如果我们把
若有,
若没有,连向根节点
并且,我刚刚遍历是不是按层遍历,即为
所以我们可以用队列实现
一个个儿子遍历过去,碰到的有这个儿子(真儿子),便可查询
但是如果没有呢(假儿子)
好办,把连接假儿子的边设为他父亲这个点的
代码就出来了
inline void bfs() {
queue <int> q;
for (int i = 0; i < 26; i ++) {
int pos = trie[1][i];//根节点的儿子(包括真假)
if (pos) {//真儿子
fail[pos] = 1;//fail数组指向根节点
q.push(pos);//入队
}
}//把根节点的儿子设为根节点,方便查询,并把他们入队
while (! q.empty()) {//开始匹配
int u = q.front();//取出队头
q.pop();//弹出
for (int i = 0; i < 26; i ++) {//一个个儿子遍历
int pos = trie[u][i];//u 的为i字母的儿子(包括真假)
if (pos) {//如果u的这个儿子是真儿子
fail[pos] = trie[fail[u]][i];//就把u这个点的儿子的fail边设置为u的fail边的那个同样的儿子
if (! fail[pos])
fail[pos] = 1;
q.push(pos);//入队
} else//如果是假儿子
trie[u][i] = trie[fail[u]][i];// 就把u的连接假儿子的边设为u这个点的fail边所指向的与其相同字母的儿子
}
}
}
接下来是查询
既然是查询哪些在文本中出现且相同的位置不同即可
我们在每次查询前跳到
所以查询也是极其简单的
代码
inline int find(char *ss) {
int len = strlen(ss + 1), pos = 1, sum = 0;
for (int i = 1; i <= len; i ++) {
int c = ss[i] - 'a';
int u = trie[pos][c];
while (u&& cnt[u] != -1) {//有这个儿子且没被搜过
sum += cnt[u];//加上答案
cnt[u] = -1;//标记
u = fail[u]; //跳到fail边
}
pos = trie[pos][c];//向下继续搞
}
return sum;
}
完整代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
const int N = 1e6 + 7;
using namespace std;
char s[N];
int n, tot = 1;
struct AC {
int trie[N][27];
int cnt[N];
int fail[N];
inline void insert(char *ss) {
int len = strlen(ss + 1), pos = 1;
for (int i = 1; i <= len; i ++) {
int c = ss[i] - 'a';
if (! trie[pos][c])
trie[pos][c] = ++ tot;
pos = trie[pos][c];
}
cnt[pos] ++;
}//构建trie 树
inline void bfs() {
queue <int> q;
for (int i = 0; i < 26; i ++) {
int pos = trie[1][i];//根节点的儿子(包括真假)
if (pos) {//真儿子
fail[pos] = 1;//fail数组指向根节点
q.push(pos);//入队
}
}//把根节点的儿子设为根节点,方便查询,并把他们入队
while (! q.empty()) {//开始匹配
int u = q.front();//取出队头
q.pop();//弹出
for (int i = 0; i < 26; i ++) {//一个个儿子遍历
int pos = trie[u][i];//u 的为i字母的儿子(包括真假)
if (pos) {//如果u的这个儿子是真儿子
fail[pos] = trie[fail[u]][i];//就把u这个点的儿子的fail边设置为u的fail边的那个同样的儿子
if (! fail[pos])
fail[pos] = 1;
q.push(pos);//入队
} else//如果是假儿子
trie[u][i] = trie[fail[u]][i];// 就把u的连接假儿子的边设为u这个点的fail边所指向的与其相同字母的儿子
}
}
}
inline int find(char *ss) {
int len = strlen(ss + 1), pos = 1, sum = 0;
for (int i = 1; i <= len; i ++) {
int c = ss[i] - 'a';
int u = trie[pos][c];
while (u&& cnt[u] != -1) {//有这个儿子且没被搜过
sum += cnt[u];//加上答案
cnt[u] = -1;//标记
u = fail[u]; //跳到fail边
}
pos = trie[pos][c];//向下继续搞
}
return sum;
}
} ac;
int main() {
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i ++) {
scanf("%s", s + 1);
ac.insert(s);
}
ac.bfs();
scanf("%s", s + 1);
cout << ac.find(s) << endl;
fclose(stdin);
fclose(stdout);
return 0;
}
所以
我们的
浅谈字符串结束了
除夕夜开始,止于