字符串指南

news/2024/9/19 19:05:29/文章来源:https://www.cnblogs.com/hisy/p/18421160

kmp

kmp 是模式串匹配的算法,本来最坏时间复杂度可以达到 $\operatorname{O}(n\times m)$,但是 kmp 可以将复杂度优化到 $\operatorname{O}(n+m)$。

kmp 有个很重要的东西,叫做 $nxt$ 失配数组。比如对于一个字符串 $s$,它的失配数组 $nxt_n$ 就是 $s$ 的最大前缀等于后缀的长度。$\operatorname{O}(n\times m)$ 的算法的劣势在于每一次失配都要重头,但是 kmp 是从上一次最大失配数组 $nxt_{nxt_n}$ 开始匹配的,这样会优化很多无意义匹配的时间。

设定一个 $pos$ 为匹配开始的位置,然后通过 $nxt$ 数组来跳,可以达到 $\operatorname{O}(n+m)$ 的时间复杂度。

int len1=strlen(s1+1);
int len2=strlen(s2+1);
int pos=0;
for(int i=2;i<=len2;++i){while(pos&&s2[i]!=s2[pos+1]){pos=nxt[pos];//跳 nxt }if(s2[i]==s2[pos+1]){++pos;//匹配成功 }nxt[i]=pos;//存入 nxt 
}
pos=0;
for(int i=1;i<=len1;++i){while(pos&&s1[i]!=s2[pos+1]){pos=nxt[pos];//跳 nxt }if(s1[i]==s2[pos+1]){++pos;//匹配成功 }if(pos==len2){printf("%d %d\n",i,i+len2-1);//一个位置 }
}

例题

首先求出 nxt 数组,然后在 nxt 数组上 dp,设 $dp_i$ 表示前缀 $i$ 的答案,只可能从 $dp_{nxt_i}$ 和 $dp_i$ 转移过来,具体开桶实现。

#include<bits/stdc++.h>
#define MAXN 500005
using namespace std;
char s[MAXN];
int nxt[MAXN],dp[MAXN],mp[MAXN];
int main(){scanf("%s",s+1);int len=strlen(s+1);int pos=0;for(int i=2;i<=len;++i){while(pos&&s[i]!=s[pos+1]){pos=nxt[pos];}if(s[i]==s[pos+1]){++pos;}nxt[i]=pos;}for(int i=1;i<=len;++i){dp[i]=i;if(mp[dp[nxt[i]]]>=i-nxt[i]){dp[i]=dp[nxt[i]];}mp[dp[i]]=i;}printf("%d",dp[len]);return 0;
}

字符串哈希

字符串哈希的思想是,通过把字符串看做 $k$ 进制数来进行存储和比较。

  • 优点:相较于直接字符比较,更加迅速,而且能够 $\operatorname{O}(1)$ 查询某段子区间的哈希值。
  • 缺点:容易冲突。

为了应对冲突,我们需要对哈希进制做一些优化,模数也需要非常极品。下面,给出预处理模版代码。

typedef unsigned long long ull;
ull base[MAXN],pre[MAXN],suf[MAXN];//进制、前缀哈希、后缀哈希
int len;
char s[MAXN];
inline ull get_pre(int l,int r){//O(1) 查询子区间哈希值 return ((pre[r]-pre[l-1]*base[r-l+1])%MOD+MOD)%MOD;
} 
inline ull get_suf(int l,int r){//O(1) 查询子区间哈希值 return ((suf[l]-suf[r+1]*base[r-l+1])%MOD+MOD)%MOD;
}
inline void prework(){base[0]=1;for(int i=1;i<MAXN;++i){base[i]=(base[i-1]*HASH)%MOD;}for(int i=1;i<=len;++i){pre[i]=(pre[i-1]*HASH+s[i]+DIF)%MOD;//加上偏移量防卡 }for(int i=len;i>=1;--i){suf[i]=(suf[i+1]*HASH+s[i]+DIF)%MOD;//加上偏移量防卡 }
}

有时候,可以采取双模哈希来进行防卡,这样被卡的几率很小。

typedef unsigned long long ull;
ull base1[MAXN],pre1[MAXN],suf1[MAXN];
ull base2[MAXN],pre2[MAXN],suf2[MAXN];//进制、前缀哈希、后缀哈希
int len;
char s[MAXN];
inline ull get_pre1(int l,int r){return ((pre1[r]-pre1[l-1]*base1[r-l+1])%MOD1+MOD1)%MOD1;
} 
inline ull get_suf1(int l,int r){return ((suf1[l]-suf1[r+1]*base1[r-l+1])%MOD1+MOD1)%MOD1;
}
inline ull get_pre2(int l,int r){//O(1) 查询子区间哈希值 return ((pre2[r]-pre2[l-1]*base2[r-l+1])%MOD2+MOD2)%MOD2;
} 
inline ull get_suf2(int l,int r){return ((suf2[l]-suf2[r+1]*base2[r-l+1])%MOD2+MOD2)%MOD2;
}
inline void prework(){base1[0]=base2[0]=1;for(int i=1;i<MAXN;++i){base1[i]=(base1[i-1]*HASH1)%MOD1;base2[i]=(base2[i-1]*HASH2)%MOD2;}for(int i=1;i<=len;++i){pre1[i]=(pre1[i-1]*HASH1+s[i]+DIF1)%MOD1;pre2[i]=(pre2[i-1]*HASH1+s[i]+DIF2)%MOD2;//加上偏移量防卡 }for(int i=len;i>=1;--i){suf1[i]=(suf1[i+1]*HASH1+s[i]+DIF1)%MOD1;suf2[i]=(suf2[i+1]*HASH2+s[i]+DIF2)%MOD2;//加上偏移量防卡 }
}

例题

这一道题目可以先把哈希值处理出来,然后发现可以枚举中间点,然后向左右二分。由于向左延伸 $n$ 格是回文串,那么 $n-1$ 格肯定也是回文串。所以满足单调性可以二分。由于本题有 Hack 数据卡自然溢,所以要打双模。

#include<bits/stdc++.h>
#define MAXN 500005
#define HASH1 31
#define HASH2 29
#define MOD1 193910017
#define MOD2 1000000009
#define ADD 131 
using namespace std;
typedef long long ull; 
int len;
char s[MAXN];
ull base1[MAXN],pre1[MAXN],suf1[MAXN];
ull base2[MAXN],pre2[MAXN],suf2[MAXN];
inline ull get_pre1(int l,int r){if(!l){return 0;}return ((pre1[r]-pre1[l-1]*base1[r-l+1])%MOD1+MOD1)%MOD1;
}
inline ull get_suf1(int l,int r){if(!l){return 0;}return ((suf1[l]-suf1[r+1]*base1[r-l+1])%MOD1+MOD1)%MOD1;
}
inline ull get_pre2(int l,int r){if(!l){return 0;}return ((pre2[r]-pre2[l-1]*base2[r-l+1])%MOD2+MOD2)%MOD2;
}
inline ull get_suf2(int l,int r){if(!l){return 0;}return ((suf2[l]-suf2[r+1]*base2[r-l+1])%MOD2+MOD2)%MOD2;
}
int main(){scanf("%d %s",&len,s+1);base1[0]=base2[0]=1;for(int i=1;i<=len;++i){base1[i]=(base1[i-1]*HASH1)%MOD1;base2[i]=(base2[i-1]*HASH2)%MOD2;pre1[i]=(pre1[i-1]*HASH1+(s[i]-'0'+ADD))%MOD1;pre2[i]=(pre2[i-1]*HASH2+(s[i]-'0'+ADD))%MOD2;}for(int i=len;i>=1;--i){suf1[i]=(suf1[i+1]*HASH1+('1'-s[i]+ADD))%MOD1;suf2[i]=(suf2[i+1]*HASH2+('1'-s[i]+ADD))%MOD2;}ull ans=0;for(int i=1;i<len;++i){int l=0,r=min(i,len-i),res=0;if(s[i]==s[i+1]){continue;}while(l<=r){ int mid=(l+r)>>1;if(get_pre1(i-mid,i)==get_suf1(i+1,i+1+mid)&&get_pre2(i-mid,i)==get_suf2(i+1,i+1+mid)){l=mid+1;res=l;}else{r=mid-1;}}ans+=res;}printf("%llu",ans);return 0;
}

字典树

字典树是一种 $k$ 叉树结构。原理是每一个节点都有一个布尔值 $flag$,判断是否是一个字符串的结尾。每一条边都有一个字符,表示前面的字符拼接起来就是字符串。这种数据结构能够 $\operatorname{O}(n)$ 查找前缀。

int cnt,trie[MAXN][MAXT],flag[MAXN];
inline int turn(char c);//字符映射函数 
inline void insert(string s){int root=0;for(int i=0;i<s.size();++i){int p=turn(s[i]);if(trie[root][p]){//有没有节点创建过 root=trie[root][p];//有就跳 }else{root=trie[root][p]=++cnt;//没有就建边 }}flag[root]=true;//标记末尾 
}
inline bool find(string s){int root=0;for(int i=0;i<s.size();++i){int p=turn(s[i]);if(!trie[root][p]){//如果没有,直接返回 return false;} root=trie[root][p];//跳 }return flag[root];//有没有这个末尾 
}

例题

这一道题目考虑贪心证明。如果在某一层,$u$ 需要比 $v$ 先,那么就建一条边。如果在下一层,出现了需要 $v$ 比 $u$ 先的情况,那就冲突了,不可能是最优。也就是判环或者用种类并查集。用 Topu 或者 Tarjan 都可以判环。

#include<bits/stdc++.h>
#define MAXN 30003
#define MAXM 26
#define MAXK MAXN*10
using namespace std;
struct node{int nxt[MAXM];bool end;
}tree[MAXK];
vector<string> ans;
int n,cnt,indeg[MAXM];
bool vis[MAXM][MAXM];
string s[MAXN];
inline void insert(string str){int root=0;for(int i=0;i<str.size();++i){int dot=str[i]-'a';if(!tree[root].nxt[dot]){tree[root].nxt[dot]=++cnt;}root=tree[root].nxt[dot];}tree[root].end=true;
}
inline bool addedge(string str){int root=0;for(int i=0;i<str.size();++i){int dot=str[i]-'a';if(tree[root].end){return false;}for(int j=0;j<MAXM;++j){if(tree[root].nxt[j]&&dot!=j&&!vis[dot][j]){++indeg[j];vis[dot][j]=true;}}root=tree[root].nxt[dot];}return true;
}
inline bool topusort(){queue<int> q;for(int i=0;i<MAXM;++i){if(!indeg[i]){q.push(i);}}while(!q.empty()){int front=q.front();q.pop();for(int i=0;i<MAXM;++i){if(vis[front][i]){--indeg[i];if(!indeg[i]){q.push(i);}}}}for(int i=0;i<MAXM;++i){if(indeg[i]){return false;}}return true;
}
int main(){ios::sync_with_stdio(false);int n;cin>>n;for(int i=1;i<=n;++i){cin>>s[i];insert(s[i]);}for(int i=1;i<=n;++i){memset(indeg,0,sizeof(indeg));memset(vis,0,sizeof(vis));if(addedge(s[i])&&topusort()){ans.push_back(s[i]);}}cout<<ans.size();for(int i=0;i<ans.size();++i){cout<<endl<<ans[i];}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/800033.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BoardLight

​​这是一个easy程度的靶机,所以博主写的也很简单,总共有2个flag。 信息收集端口扫描: ​​发现开放了22,80端口 Web渗透 ​​最底部发现域名 board.htb写进/etc/hosts​文件中 sudo echo "10.10.11.11 board.htb" | sudo tee -a /etc/hosts 对他进行子域名爆破…

LLM学习笔记-长度外推技术

长度外推为在不需要对模型进行额外训练的情况下,模型可以处理更长的序列。本篇文章主要介绍目前大模型用到的一些长度外推技术,包括以RoPE为基础进行位置插值、NTK-aware、动态NTK、NTK-by-parts 和YaRN。关于RoPE,可参见我的上一篇博客LLM学习笔记-位置编码篇 位置插值 回想…

裘立帆-第一次作业

在博客园建立个人技术博客,完善个人信息及博客设置,并发布一篇包含自我介绍、技能树与技术偏好、课程期望的随笔,以此作为课程参与的开始。这个作业属于哪个课程 https://edu.cnblogs.com/campus/zjlg/rjjc/这个作业的目标 熟悉博客的写作和使用,将自己介绍给老师和助教姓名…

章14——集合——集合体系

目录两个难点 底层机制,和不同应用场景下的选择集合体系图,需要背诵!总结: 1、集合主要是两组(单列集合、双列集合) 2、Collection 接口有两个重要的子接口 List Set, 他们的实现子列都是单列集合 3、Map 接口实现的子类是双列集合,存放的是key,value 4、上述两张图要记…

使用 VSCode 调试 Zig

首要条件是你本地需要安装MinGW-w64. 可以参考MinGW-w64安装教程——著名C/C++编译器GCC的Windows版本 - jack_Meng - 博客园 (cnblogs.com) 这里有几点需要注意,在2024年9月时,我没有找exe的安装,你需要在github下载 Releases niXman/mingw-builds-binaries (github.com) …

Go 入门指南:8.5. map 的排序

原创 吃个大西瓜 Coding Big Tree2024年09月19日 08:00 云南map 默认是无序的,不管是按照 key 还是按照 value 默认都不排序(详见第 8.3 节)。 如果你想为 map 排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包,详见第 7.6.6 节),然后可以使用…

频率响应9

频率响应 公式推导 正弦输入的一般形式 \[u(t)=Asin(\omega_i)+bcos(\omega_i) \]整理 \[u(t)=M_i sin(\omega_i t + \phi_i) \\其中 \phi_i=arctan \frac{B}{A} \qquad ,M_i=\sqrt{A^2+B^2} \]输入到系统 G(s) \[\begin{aligned} U(s)& =\mathcal{L}[u\left(t\right)]=\…

vscode 搜索框3个按钮分别代表什么

https://blog.csdn.net/u012292754/article/details/108307288相信坚持的力量,日复一日的习惯.

数据库系统 1 关系数据库

数据库系统 1 关系数据库 三层体系结构外部层:数据库的用户视图 概念层:数据库的整体视图,提供内、外部层的映射和必要的独立性所有实体,实体的属性和实体间的联系 数据的约束 数据的语义信息 安全性和完整性信息内部层:数据库在计算机上的物理表示数据独立性 三层体系的主…

记录一次首页优化的经历

公司最近要进行多品牌合一,原来五个品牌的app要合并为一个。品牌立项、审批、方案确定,历史数据迁移、前期的基础工程搭建,兼容以及涉及三方的交互以及改造,需求梳理等也都基本完成,原来计划9月中旬进行上线,但是上线后服务端的压测一直通不过-首页抗不过太高的并发。app…

软工作业3:结对项目——实现一个自动生成小学四则运算题目的命令行程序

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/这个作业要求在哪里 结对项目 - 作业 - 计科22级34班 - 班级博客 - 博客园 (cnblogs.com)这个作业的目标 结对项目——实现一个自动生成小学四则运算题目的命令行程序成员1 陈奕奕 3222004552成员2 林闰…

基于LangChain手工测试用例转App自动化测试生成工具

在传统编写 App 自动化测试用例的过程中,基本都是需要测试工程师,根据功能测试用例转换为自动化测试的用例。市面上自动生成 Web 或 App 自动化测试用例的产品无非也都是通过录制的方式,获取操作人的行为操作,从而记录测试用例。整个过程类似于但是通常录制出来的用例可用性…