字符串算法并不多,KMP,trie,AC自动机就是其中几个最经典的。字符串的题目灵活多变也有许多套路,需要多做题才能体会。这里收集了许多前辈的题目做个集合,方便自己回忆。

KMP题目:https://blog.csdn.net/qq_38891827/article/details/80501506

Trie树题目:https://blog.csdn.net/qq_38891827/article/details/80532462

AC自动机:模板https://www.luogu.org/blog/42196/qiang-shi-tu-xie-ac-zi-dong-ji

AC自动机题目集:https://www.cnblogs.com/kuangbin/p/3164106.html

KMP:

LuoguP3375 KMP模板

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=+;
char a[N],b[N];
int n,m,nxt[N],f[N],g[N]; void get_next() {
nxt[]=;
for (int i=,j=;i<=n;i++) {
while (j> && a[j+]!=a[i]) j=nxt[j];
if (a[j+]==a[i]) j++;
nxt[i]=j;
}
} void KMP() {
for (int i=,j=;i<=m;i++) {
while (j> && (j==n || b[i]!=a[j+])) j=nxt[j];
if (b[i]==a[j+]) j++;
f[i]=j;
if (f[i]==n) g[i]=; //这就是A在B中的某一次出现
}
} int main()
{
scanf("%s",b+); m=strlen(b+);
scanf("%s",a+); n=strlen(a+);
get_next();
KMP();
for (int i=;i<=m;i++)
if (g[i]==) cout<<i-n+<<endl;
for (int i=;i<=n;i++) cout<<nxt[i]<<" ";
return ;
}

HDU-2087

给出A串和B串,问B串在A串中出现次数。不能重叠!!!KMP裸题,允许重叠的话匹配成功时j=nxt[j],不允许重叠的话匹配成功时j=0。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e3+;
char a[N],b[N];
int n,m,nxt[N],f[N],g[N]; void get_next() {
nxt[]=;
for (int i=,j=;i<=n;i++) {
while (j> && a[j+]!=a[i]) j=nxt[j];
if (a[j+]==a[i]) j++;
nxt[i]=j;
}
} int KMP() {
int ret=;
for (int i=,j=;i<=m;i++) {
while (j> && (j==n || b[i]!=a[j+])) j=nxt[j];
if (b[i]==a[j+]) j++;
f[i]=j;
if (f[i]==n) ret++,j=; //匹配成功:因为不允许重叠所以要把j=0
}
return ret;
} int main()
{
while (scanf("%s",b+) && b[]!='#') {
memset(nxt,,sizeof(nxt));
scanf("%s",a+);
n=strlen(a+); m=strlen(b+);
get_next();
cout<<KMP()<<endl;
}
return ;
}

POJ-1961/HDU-1358

给出一个串,问每个前缀的最小循环节以及循环节大小。KMP经典题,对于s[1-i],最小循环节就是i-nxt[i],循环节大小就是i/(i-nxt[i])。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=+;
char S[N];
int n,nxt[N],f[N]; void get_next() {
nxt[]=;
for (int i=,j=;i<=n;i++) {
while (j> && S[j+]!=S[i]) j=nxt[j];
if (S[j+]==S[i]) j++;
nxt[i]=j;
}
} int main()
{
int T=;
while (scanf("%d",&n) && n) {
printf("Test case #%d\n", ++T);
scanf("%s",S+);
get_next();
for (int i=;i<=n;i++)
if (i%(i-nxt[i])== && i/(i-nxt[i])>)
printf("%d %d\n",i,i/(i-nxt[i]));
printf("\n");
}
return ;
}

HDU-3746

添加最少的字符使原字符串变成周期至少为2的循环字符串。求出最小循环节lop,当且仅当n%lop==0 && n/lop>1才不需要添加,否则添加 lop-n%lop个字符。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=+;
char S[N];
int n,nxt[N],f[N]; void get_next() {
nxt[]=;
for (int i=,j=;i<=n;i++) {
while (j> && S[j+]!=S[i]) j=nxt[j];
if (S[j+]==S[i]) j++;
nxt[i]=j;
}
} int main()
{
int T; scanf("%d",&T);
while (T--) {
scanf("%s",S+); n=strlen(S+);
for (int i=;i<=n;i++) nxt[i]=;
get_next();
int lop=n-nxt[n];
if (n%lop== && n/lop>) puts("");
else printf("%d\n",lop-n%lop);
}
return ;
}

POJ-2752

给定一个字符串,输出该串所有的前后缀相同的前缀位置。正好nxt数组就是前后缀相同大小,但是注意到nxt只记录了最大前后缀,但是题目要求输出所有前后缀,所以我们递归输出所以的nxt即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=4e5+;
char a[N],b[N];
int n,m,nxt[N],f[N],g[N]; void get_next() {
nxt[]=;
for (int i=,j=;i<=n;i++) {
while (j> && a[j+]!=a[i]) j=nxt[j];
if (a[j+]==a[i]) j++;
nxt[i]=j;
}
} void dfs(int x) {
if (x==) return;
dfs(nxt[x]);
printf("%d ",x);
} int main()
{
while (scanf("%s",a+)!=EOF) {
n=strlen(a+);
for (int i=;i<=n;i++) nxt[i]=;
get_next();
dfs(n); puts("");
}
return ;
}

Trie:

HDU-1251

先给出一堆单词然后询问,每个询问输入一个字符串s问以s为前缀的单词个数。询问有多少个单词是s的前缀的话就只在单词结束点++,询问s是多少个单词的前缀的话就在单词每一个点都+1代表访问次数。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=+;
int trie[*N][],tot=;
int n,m,endp[*N]; void insert(char *str) {
int p=;
for (int i=;i<strlen(str);i++) {
int x=str[i]-'a';
if (!trie[p][x]) trie[p][x]=++tot;
p=trie[p][x];
endp[p]++; //访问次数
}
//endp[p]++; //单词个数
} int search(char *str) {
int p=;
for (int i=;i<strlen(str);i++) {
int x=str[i]-'a';
p=trie[p][x];
if (p==) break;
}
return endp[p];
} int main()
{
char s[];
bool ok=;
while (gets(s)) {
if (s[]=='\0') {ok=; continue;}
if (!ok) insert(s);
else printf("%d\n",search(s));
}
return ;
}

HDU-2072

问不同单词个数,一边查询只有没出现过的单词插入即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+;
int trie[*N][],tot=;
int n,m,endp[*N];
string ss,s,str1; void insert(string str) {
int p=;
for (int i=;i<str.length();i++) {
int x=str[i]-'a';
if (!trie[p][x]) trie[p][x]=++tot;
p=trie[p][x];
}
endp[p]++; //单词个数
} int search(string str) {
int p=;
for (int i=;i<str.length();i++) {
int x=str[i]-'a';
p=trie[p][x];
if (p==) break;
}
return endp[p];
} int main()
{
ios::sync_with_stdio(false);
while(getline(cin,str1)) {
if(str1=="#") break;
stringstream ss(str1);
int ans=;
while (ss>>s) {
if (search(s)==) ans++,insert(s);
}
printf("%d\n",ans);
for (int i=;i<=tot;i++) {
endp[i]=;
for (int j=;j<=;j++) trie[i][j]=;
}
tot=;
}
return ;
}

POJ-2001

给一堆单词问每个单词独有的前缀。先把全部单词插入到trie树中,trie树的endp[i]代表该点被访问次数,询问时直到访问次数为1的就是该单词独有的。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=+;
int trie[*N][],tot=;
int n,m,num=,endp[*N];
char s[][]; void insert(char *str) {
int p=;
for (int i=;i<strlen(str);i++) {
int x=str[i]-'a';
if (!trie[p][x]) trie[p][x]=++tot;
p=trie[p][x];
endp[p]++;
}
//endp[p]++; //单词个数
} int search(char *str) {
int p=,ans=;
for (int i=;i<strlen(str);i++) {
int x=str[i]-'a';
p=trie[p][x];
if (p==) break;
if (endp[p]==) return i+;
}
return strlen(str);
} int main()
{
while (scanf("%s",s[num])!=EOF) {
insert(s[num]);
num++;
}
num--;
for (int i=;i<=num;i++,puts("")) {
int tmp=search(s[i]);
printf("%s ",s[i]);
for (int j=;j<tmp;j++) printf("%c",s[i][j]);
}
return ;
}

POJ-3630

给一堆字符串,问是否存在某个单词是某个单词的前缀。老规矩先把全部单词插到trie树,然后询问看每次单词是否有出现次数>1的单词。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=+;
int trie[*N][],tot=;
int n,m,endp[*N];
char s[N][]; void insert(char *str) {
int p=;
for (int i=;i<strlen(str);i++) {
int x=str[i]-'';
if (!trie[p][x]) trie[p][x]=++tot;
p=trie[p][x];
}
endp[p]++; //单词个数
} int search(char *str) {
int p=,ans=;
for (int i=;i<strlen(str);i++) {
int x=str[i]-'';
p=trie[p][x];
if (p==) break;
ans+=endp[p];
}
return ans;
} int main()
{
int T; cin>>T;
while (T--) {
scanf("%d",&n);
for (int i=;i<=n;i++) scanf("%s",s[i]),insert(s[i]);
bool ok=;
for (int i=;i<=n;i++)
if (search(s[i])>) { ok=; break; }
printf("%s\n",ok?"YES":"NO"); for (int i=;i<=tot;i++) {
endp[i]=;
for (int j=;j<=;j++) trie[i][j]=;
}
tot=;
}
return ;
}

AC自动机:

洛谷P3808AC自动机模板1,求有多少个模式串在文本串里出现过。

#include<bits/stdc++.h>
using namespace std;
const int N=+;
int n,cnt=;
char s[N];
struct node{
int val;
int vis[];
int fail;
}ac[N]; void Trie_init() { cnt=; memset(ac[].vis,,sizeof(ac[].vis)); }
int idx(char c) { return c-'a'; } void Insert(char *s) {
int u=,len=strlen(s);
for (int i=;i<len;i++) {
int c=idx(s[i]);
if (!ac[u].vis[c]) {
ac[u].vis[c]=++cnt;
ac[cnt].val=ac[cnt].fail=;
memset(ac[cnt].vis,,sizeof(ac[cnt].vis));
}
u=ac[u].vis[c];
}
ac[u].val++;
} queue<int> q;
void Get_fail() {
while (!q.empty()) q.pop();
for (int i=;i<;i++) {
int u=ac[].vis[i];
if (u) { ac[u].fail=; q.push(u); }
}
while (!q.empty()) {
int u=q.front(); q.pop();
for (int i=;i<;i++) {
int v=ac[u].vis[i];
if (v) {
ac[v].fail=ac[ac[u].fail].vis[i];
q.push(v);
} else ac[u].vis[i]=ac[ac[u].fail].vis[i];
}
}
} int query(char *s) {
int len=strlen(s);
int u=,ans=;
for (int i=;i<len;i++) {
u=ac[u].vis[idx(s[i])];
for (int t=u;t&&ac[t].val!=-;t=ac[t].fail) {
ans+=ac[t].val;
ac[t].val=-;
}
}
return ans;
} int main()
{
Trie_init();
scanf("%d",&n);
for (int i=;i<=n;i++) {
scanf("%s",s);
Insert(s);
}
Get_fail();
scanf("%s",s);
cout<<query(s)<<endl;
return ;
}

洛谷P3796AC自动机模板2,找出哪些模式串在文本串T中出现的次数最多并输出。

#include<bits/stdc++.h>
using namespace std;
const int N=+;
int n,cnt=,ans[];
char p[N],s[][];
struct node{
int val;
int vis[];
int fail;
}ac[N]; void Trie_init() { cnt=; memset(ac[].vis,,sizeof(ac[].vis)); }
int idx(char c) { return c-'a'; } void Insert(char *s,int id) {
int u=,len=strlen(s);
for (int i=;i<len;i++) {
int c=idx(s[i]);
if (!ac[u].vis[c]) {
ac[u].vis[c]=++cnt;
ac[cnt].val=ac[cnt].fail=;
memset(ac[cnt].vis,,sizeof(ac[cnt].vis));
}
u=ac[u].vis[c];
}
ac[u].val=id;
} queue<int> q;
void Get_fail() {
while (!q.empty()) q.pop();
for (int i=;i<;i++) {
int u=ac[].vis[i];
if (u) { ac[u].fail=; q.push(u); }
}
while (!q.empty()) {
int u=q.front(); q.pop();
for (int i=;i<;i++) {
int v=ac[u].vis[i];
if (v) {
ac[v].fail=ac[ac[u].fail].vis[i];
q.push(v);
} else ac[u].vis[i]=ac[ac[u].fail].vis[i];
}
}
} int query(char *s) {
int len=strlen(s);
int u=,ret=;
for (int i=;i<len;i++) {
u=ac[u].vis[idx(s[i])];
for (int t=u;t&&ac[t].val!=-;t=ac[t].fail) {
if (ac[t].val) ret=max(ret,++ans[ac[t].val]); //ans[i]为模式串i的出现次数
}
}
return ret; //返回出现最多的次数
} int main()
{
while (scanf("%d",&n) && n) {
Trie_init();
for (int i=;i<=n;i++) {
scanf("%s",s[i]);
Insert(s[i],i);
}
Get_fail(); scanf("%s",p);
memset(ans,,sizeof(ans));
int Max=query(p);
printf("%d\n",Max);
for (int i=;i<=n;i++)
if (ans[i]==Max) printf("%s\n",s[i]);
}
return ;
}

洛谷P5357AC自动机模板3,求出每个模式串 Ti​ 在 S 中出现的次数。这一题的重复单词有影响,所以我们不能用上一题记录单词出现次数,而是应该计算该位置的单词出现次数,然后对于每个单词记录它在Trie树上的位置,输出。

到这里也还不能够获得AC,会TLE。洛谷题解上有几种优化的方式,我这里选择的是拓扑排序优化建图的方式。

KMP,Trie,AC自动机题目集的更多相关文章

  1. AC自动机-题目集合

    AC自动机-题目集合 模板 如果你想要学习AC自动机,推荐一些学习资料. 学习可以看这篇博客 http://blog.csdn.net/niushuai666/article/details/7002 ...

  2. KMP,HASH,Trie,AC自动机

    我做个总结算了下午看了一下AC自动机和学习我的大生物(当然是多谢鑫神了)..完了要崩.. 1 KMP 只要是学过的人都觉得比较简单吧 但是学不会的人就感觉很难了,我是那种顿悟的然后感觉非常简单的人过程 ...

  3. KMP与AC自动机

    KMP算法主要思想就是预处理出失配函数, 从而减少匹配失败时的回溯, 复杂度是$\Theta(m+n)$, 已达到理论下界 c++代码如下 int n, f[N]; char t[N], p[N]; ...

  4. KMP与AC自动机模板

    HDU 1711 Number Sequence(KMP模板题) http://acm.hdu.edu.cn/showproblem.php?pid=1711 #include<bits/std ...

  5. [AC自动机]题目合计

    我只是想记一下最近写的题目而已喵~ 题解什么的才懒得写呢~ [poj 1625]Censored! 这题注意一个地方,就是输入数据中可能有 ASCII 大于 128 的情况,也就是说用 char 读入 ...

  6. AC自动机题目汇总

    POJ 4052 ZJU 3430 HDU 4117 HNU 10104 HDU 2457 HNU 11187 ZJU 3545 HDU 3341

  7. 关于Trie KMP AC自动机

    个人认为trie,KMP,AC自动机是思想非常明确的,AC自动机的性质是与KMP算法的思想类似的(失配后跳转) 而KMP是线性的,AC自动机是在tire树上跑KMP,为方便那些不会用指针的小朋友(我也 ...

  8. AC自动机及KMP练习

    好久都没敲过KMP和AC自动机了.以前只会敲个kuangbin牌板子套题.现在重新写了自己的板子加深了印象.并且刷了一些题来增加自己的理解. KMP网上教程很多,但我的建议还是先看AC自动机(Trie ...

  9. BZOJ.4820.[SDOI2017]硬币游戏(思路 高斯消元 哈希/AC自动机/KMP)

    BZOJ 洛谷 建出AC自动机,每个点向两个儿子连边,可以得到一张有向图.参照 [SDOI2012]走迷宫 可以得到一个\(Tarjan\)+高斯消元的\(O((nm)^3)\)的做法.(理论有\(6 ...

随机推荐

  1. hdu 6127 : Hard challenge (2017 多校第七场 1008)(计算几何)

    题目链接 题意:二维平面上有n个点(没有重叠,都不在原点,任意两点连线不过原点),每个点有一个权值,用一条过原点的直线把他们划分成两部分,使两部分的权值和的乘积最大.输出最大的乘积. 极角排序后,将原 ...

  2. git 常用命令记录

    删除远程分支 git push origin --delete 远程分支名 删除本地分支 git branch -d 本地分支名 从master新建分支 git checkout -b 新分支名 建立 ...

  3. python 全栈开发,Day45(html介绍和head标签,body标签中相关标签)

    一.html介绍 1.web标准 web准备介绍: w3c:万维网联盟组织,用来指定web标准的机构(组织) web标准:制作网页遵循的规范 web准备规范的分类:结构标准.表现标准.行为标准. 结构 ...

  4. 【説明する】STL

    作为C++标准不可缺少的一部分,STL应该是渗透在C++程序的角角落落里的. STL不是实验室里的宠儿,也不是程序员桌上的摆设,她的激动人心并非昙花一现. 所以今天要整理的东西就是STL!(orz 杨 ...

  5. LNMP环境搭建最好用的两种方法(亲测)

    经历了一个PHP服务器项目,手动编译部署PHP,Swoole环境太让人郁闷了,所以尝试过两种不错的方法,分享出来方便同样经历痛苦的coder. 第一种方式: 安装LNMP按照这里的步骤执行,网址戳我 ...

  6. IDEA设置Ctrl+滚轮调整字体大小(转载)

    按Ctrl+Shift+A,出现搜索框 输入mouse: 点击打开这个设置:勾选 点击ok,之后就可以通过Ctrl+滚轮 调整字体大小了. 转载自:http://www.cnblogs.com/LUA ...

  7. svn 中的add 和commit命令有何区别

    add 功能:向文件拷贝所在的文件夹中添加新的文件,并作出标识,是新添加的,下一步提交时将一并提交到Subversion版本库中去.简单的说就是将一新文件加入svn,你添加再提交后该文件就进入subv ...

  8. vue.jsc初体验

    Vue 1.安装脚手架 (1)npm install -g vue-cli (2)Vue -v //查看是否安装成功 (3)Vue init webpack name(名称) (4)Npm insta ...

  9. EL表达式(三)自定义 EL 表达式

    自定义EL函数(静态方法): 编写步骤: 1.编写一个Java类,提供一个静态方法 import java.util.List; public class GetLength { public sta ...

  10. Mac009--Axure RP安装

    Mac--Axure RP安装 一.下载Axure RP8.0 下载网址:https://www.axure.com/download  (下载mac版本) Axure RP说明: Axure RP是 ...