字符串
对串的操作:
从下标1开始数
insert(x)插入到第x个位置之前(新插入的元素位置是x)
delete(x)是正好删除第x个
最小表示法模板:洛谷P1368(这题我之前写过?一点印象都没了,这就是不复习的下场)(算了困了明早再写,今天周日,摸了)
#include <bits/stdc++.h> using namespace std; int n,a[600005]; int f(){ int i=1,j=2,k=0; while(i<=n&&j<=n){ if(a[i+k]>a[j+k])i+=k+1,k=0; else if(a[i+k]<a[j+k])j+=k+1,k=0; else if(a[i+k]==a[j+k])k++; if(i==j)i++; } return min(i,j); } int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; a[i+n]=a[i]; } for(int i=f(),j=0;j<n;j++){ cout<<a[i+j]<<' '; } return 0; }
提示:三指针+延长一倍+注意:指针移动以后可能会撞在一起
很奇怪,最小表示法里面有个细节一直不能理解:
初始条件:i=1,j=2,k=0,若a[i+k]>a[j+k],为什么i+=k+1这一步是合理的,如何确定i->i+k+1之间的循环同构串一定比j指向的更大(搞不懂)
搞懂了!
解释:i之所以能够自增k+1,是因为i->i+k是与j->j+k完全相同的,所以i直接跳到i+k+1去,跳过的那些就交给j去遍历吧!
马拉车算法:
洛谷P3805
#include <bits/stdc++.h> using namespace std; char str[22000010]; int cnt,C=-1,R=-1,r[22000010],res; void in_() { str[cnt++] = '#'; char c = getchar(); while (c >= 'a' && c <= 'z')str[cnt++] = c, str[cnt++] = '#', c = getchar(); } int process() { for (int i = 0; i < cnt; i++) { r[i] = i > R ? 1 : min(r[C * 2 - i], (R - i)); while (i + r[i] < cnt && i - r[i] >= 0 && str[i + r[i]] == str[i - r[i]]) r[i]++; if (i + r[i] > R) R = i + r[i], C = i; res = res > r[i] ? res : r[i]; } return res - 1; } int main() { in_(); cout<<process(); return 0; }
KMP:
#include <bits/stdc++.h> using namespace std; string a, b; int len_a, len_b; int ne[1000005]; void get_next() { string c = b + ‘0’;//防止越界 for (int i = 2, j = 0; i <= len_b; i++) {//j是前跳的指针 while (j && c[i] != c[j + 1]) j = ne[j];//i和j指向的匹配不上,j一直前跳,直到匹配成功或者j已经到了最前面,无处可跳为止 if (c[i] == c[j + 1])j++;//匹配上了 ne[i] = j;//记录下来 //cout<<"第"<<i<<"个字符的最长前后缀长度为"<<j<<endl; } } void process() { for (int i = 1, j = 0; i <= len_a; i++) { while (j && a[i] != b[j + 1])j = ne[j];//匹配不成功就一直前跳,直到找到或者无处可跳为止,j的意义有两个:1.b与a已经匹配到的位置,2.最长前后缀的长度 if (a[i] == b[j + 1])j++;//找到 if (j == len_b) cout << i - j + 1 << endl;//完成了匹配 } for (int i = 1; i <= len_b;i++) { cout << ne[i] << " "; } } int main() { cin >> a >> b; len_a = a.size(), len_b = b.size(); a = '0' + a; b = '0' + b;//把下标排列整齐 get_next(); process(); return 0; }
来啊,让我们写个修改版:
#include <bits/stdc++.h> using namespace std; string a, b; int len_a, len_b; int ne[1000005]; void get_next() { int i = 1, j = 0; while (i < len_b) { if (j == 0 || b[i] == b[j]) { i++, j++; if (b[i] != b[j])ne[i] = j; else ne[i] = ne[j]; } else j = ne[j];// } } void process() { int i = 1, j = 1;//i在主串上跑,j在模式串上跑 while (i <= len_a && j <= len_b) { if (j == 0 || a[i] == b[j])i++, j++; //状况1:j==0,j:“我都跑到头了呀大哥,你别不动了,你稍微动一动吧,于是i和j都自增,i变下一个,j变1,从第一个元素开始遍历”; //状况2:匹配成功了,自增,匹配下一个 else j = ne[j];//匹配失败,j指向fail指针指向的位置 } if (j > len_b)cout << i - len_b;//i此时指向匹配成功之后的下一个字符 else cout << "不存在"; } int main() { cin >> a >> b; len_a = a.size(), len_b = b.size(); a = '0' + a; b = '0' + b;//把下标排列整齐 get_next(); process(); return 0; }
以上是课本上的代码复现
手动求ne数组加强版本的时候,先把无加强版ne数组求出来,然后再求加强版ne数组。
做法:观察一下s[i]和s[ne[i]]是否相同,相同的话neplus[i]就是s[ne[i]],不同的话就是ne[i]本身。
PTA的字符串计数从1开始,而非0
洛谷P3375
AC自动机:
建立Trie树,插入字符串:
//创建字典树 int idx, cnt[N], ch[N][26]; void insert(string s) { int p = 0;//从第0个结点,也就是根节点开始 for (int i = 0; s[i]; i++) { int j = s[i] - 'a';//把字符映射为数字 if (!ch[p][j])ch[p][j] = ++idx;//如果第p个结点处没有字符j的分支,则创建,并给它一个编号id p = ch[p][j];//以这个编号为id的结点为新结点 } cnt[p]++;//最后这个结点的插入次数加一 }
查找字符串:
int query(string s) { int p = 0; for (int i = 0; s[i]; i++) { int j = s[i] - 'a'; if (!ch[p][j])return 0; p = ch[p][j]; } return cnt[p]; }
完整AC自动机:洛谷P3808
(59条消息) AC自动机 算法详解(图解)及模板_bestsort的博客-CSDN博客_ac自动机算法
代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2 * 1e6 + 9; int trie[maxn][26]; //字典树 int cntword[maxn]; //记录该单词出现次数 int fail[maxn]; //失败时的回溯指针 int cnt = 0; void insert(string s) { int p = 0; for (int i = 0; s[i]; i++) { int j = s[i] - 'a'; if (!trie[p][j])trie[p][j] = ++cnt; p = trie[p][j]; } cntword[p]++; } void getFail() { queue <int>q; for (int i = 0; i < 26; i++) { //将第二层所有出现了的字母扔进队列 if (trie[0][i]) { fail[trie[0][i]] = 0; q.push(trie[0][i]); } } //fail[now] ->当前节点now的失败指针指向的地方 //tire[now][i] ->下一个字母为i + 'a'的节点的下标为tire[now][i] while (!q.empty()) { int now = q.front(); q.pop(); for (int i = 0; i < 26; i++) { //查询26个字母 if (trie[now][i]) { //如果有这个子节点为字母i+'a',则让这个节点的fail指针指向他父亲节点的fail指针所指向的那个节点的子节点 //有子结点,为子结点建立回跳边,使用四边形规则 fail[trie[now][i]] = trie[fail[now]][i]; q.push(trie[now][i]);//子结点入队 } else //否则就让当前节点的这个子节点指向当前节点fail指针的这个子节点 //没子结点,为自己建立转移边,使用三角形规则 trie[now][i] = trie[fail[now]][i]; } } } int query(string s) { int now = 0, ans = 0; for (int i = 0; i < s.size(); i++) { //遍历文本串 now = trie[now][s[i] - 'a']; //从s[i]点开始寻找 for (int j = now; j && cntword[j] != -1; j = fail[j]) { //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过). ans += cntword[j]; cntword[j] = -1; //将遍历后的节点标记,防止重复计算 } } return ans; } int main() { int n; string s; cin >> n; for (int i = 0; i < n; i++) { cin >> s; insert(s); } fail[0] = 0; getFail(); cin >> s; cout << query(s) << endl; return 0; }
now是一整层一整层地下跳,j是层层往上找有没有后缀与now所指结点的后缀相同的模式串,这一层j没有就再上一层,直到上不去了或者已经走过了为止。
基础操作:
#include<bits/stdc++.h> using namespace std; int main(){ string s1; while(getline(cin,s1)){ cout<<s1<<endl; //遍历 cout<<"all elements of string:"<<endl; for(auto c:s1){ cout<<c<<' '; } cout<<endl; //子 string s2=s1.substr(0,3);//从pos开始的len个字符 cout<<"substring:"<<endl; cout<<s2<<endl; //加 string s3(s1); s3=s3.insert(3,s3); cout<<"string inserted:"<<s3<<endl; //删 s3.erase(3,9);//从3到9 cout<<"after erasing:"<<s3<<endl; //查 auto temp=s3.find(string("456"),2);//从第pos个下标开始找 cout<<"first appearing position in the string:"; if(temp!=string::npos){ cout<< temp<<endl; } else{ cout<<"not found"<<endl; } //替换 s3.replace(s3.find("456"),3,"66666666"); cout<<"after replace:"<<s3<<endl; } return 0; }