首页 > 其他分享 >字符串

字符串

时间:2024-07-02 19:20:06浏览次数:20  
标签:子串 ll endpos nex 字符串 mathtt dp

之前就是史,重新来写,字符串还是有必要学的。

KMP

用于文本串匹配。其和暴力的区别在于失配后会从一个特定位置重新开始匹配而不是从头开始,从而节约时间。
这个失配数组也就是 \(nex_i\) 表示 \(S[\mathbf{1}\dots i]\) 的最长 \(\mathtt{border}\) 长度,建出来之后相当于一个自动机。
记住代码实现就行了,实际上大部分情况也可以二分哈希替代,但毕竟多个 \(\mathtt{log}\)。
模板题代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=1145140,M=1919810,inf=2147483646;
ll n,m;
char s[N],t[N];
ll nex[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>(s+1)>>(t+1);
	n=strlen(s+1),m=strlen(t+1);
	ll p=0;
	for(int i=2;i<=m;++i){
		while(p&&t[i]!=t[p+1]) p=nex[p];
		if(t[i]==t[p+1]) ++p;
		nex[i]=p;
	}
	p=0;
	for(int i=1;i<=n;++i){
		while(p&&s[i]!=t[p+1]) p=nex[p];
		if(s[i]==t[p+1]) ++p;
		if(p==m){
			cout<<i-m+1<<'\n';
			p=nex[p];
		}
	}
	for(int i=1;i<=m;++i) cout<<nex[i]<<" ";
	return 0;
}

P3426

一个思维题,只需要KMP,不需要后缀数据结构也能做。
首先读完题可以发现,满足题意的子串一定是原串的 \(\mathtt{border}\)。暴力的想法就是去枚举所有 \(\mathtt{border}\) 然后用 KMP 判断匹配的位置之差是否大于串的长度。
考虑进行 dp(想到这一步有点跳跃),设 \(dp[i]\) 为前 \(i\) 个字符的最小答案。对于某个 \(nex_i=\mathbf{0}\),也就是没有 \(\mathtt{boeder}\) 的情况时,就只能 \(dp_i=i\),这是显然的。然后考虑答案转移,经过手玩可以发现,\(dp[i]=dp[nex[i]]\),这是可以证明的,但我懒。
那么转移关系就是 \(dp[i]=i\) 或 \(dp[i]=dp[nex[i]]\)。当前面有一个点 \(j\) 可以被 \(dp[nex[i]]\) 覆盖到且能覆盖到当前要覆盖的后缀部分,那么 \(dp[i]\) 就可以从 \(dp[nex[i]]\) 转移过来,这样是一定不劣的。维护这个过程的话可以开一个桶来记录每种长度的 \(\mathtt{border}\) 最多能覆盖到哪里。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=5*114514,M=1919810,inf=2147483646;
ll n;
char s[N];
ll nex[N],dp[N],bu[N]; //前i个字符的答案 
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>(s+1); n=strlen(s+1);
	ll p=0;
	for(int i=2;i<=n;++i){
		while(p&&s[i]!=s[p+1]) p=nex[p];
		if(s[i]==s[p+1]) ++p;
		nex[i]=p;
	}
	dp[1]=bu[1]=1;
	for(int i=2;i<=n;++i){
		if(bu[dp[nex[i]]]+dp[nex[i]]>=i){
			dp[i]=dp[nex[i]];
			bu[dp[nex[i]]]=i;
		}
		else dp[i]=bu[i]=i;
	}
	cout<<dp[n];
	return 0;
}

后缀自动机(SAM)

定义不想多写了,感性理解,SAM 就是个可以 \(O(n)\) 构造并在上面可以 \(O(n)\) 完成操作的有向图,存了原串所有的后缀,有很多有用的性质,能拿来做很多题。

结束位置 \(\mathtt{endpos}\)

对于原串 \(s\) 的任意非空子串 \(t\),其 \(\mathtt{endpos}(t)\) 为 \(t\) 在 \(s\) 中的所有结束位置。两个子串 \(t_{\mathtt{1}},t_{\mathtt{2}}\) 的 \(\mathtt{endpos}\) 集合可能相等,这样我们可以把所有字串根据 \(\mathtt{endpos}\) 集合划分为若干个等价类
显然,SAM 中的每个状态对应一个或多个 \(\mathtt{endpos}\) 相同的子串。换句话说,SAM 中的状态数等于所有子串的等价类的个数,再加上初始状态。SAM 的状态个数等价于 \(\mathtt{endpos}\) 相同的一个或多个子串所组成的集合的个数 \(\mathtt{+1}\)。同时关于 \(\mathtt{endpos}\) 有一些性质,我就简略写了,一下都考虑 \(s\) 的两个子串 \(u,w\) 并假设 \(|u|\le|w|\):

\(\mathbf{1}\).若 \(\mathtt{endpos}(u)\cap\mathtt{endpos}(w)=\oslash\),则 \(u\) 不是 \(w\) 后缀,反之则是。

\(\mathbf{2}\).一个等价类中的所有串的长度的集合恰能覆盖一整段区间 \([x,y]\)。

后缀链接 \(\mathtt{link}\)

有点像 AC自动机的失配链接。

待补。

标签:子串,ll,endpos,nex,字符串,mathtt,dp
From: https://www.cnblogs.com/heshuwan/p/18280022

相关文章

  • 字符串的类区别、自动扩容、深浅拷贝
    1、stringstringBufferstrngBuilder区别可变性:string中的vlaue值是final修饰的,是一个不可变的类,每一次修改string的值的时候,都会产生一个新的对象。而stringBuffer和strngBuilder是一个可变类。字符串的变更不会产生新的对象。线程安全性:string是一个不可变的类,所......
  • 周下载量3000多万的npm包---nanoid(uuid的竞争对手),生产随机字符串,体积更小,可以自定义
    https://www.npmjs.com/package/nanoid体积更小,可以自定义字符集<scriptsetup>import{onMounted}from'vue'import{nanoid,customAlphabet}from'nanoid'onMounted(()=>{letid1=nanoid()console.log(id1)letcustomNanoid......
  • rust 字符串拼接
    提问rust字符串拼接方式回答format!("{}{}",s1,s2);fnmain(){lets1="Hello";lets2="World";//Usingformat!macroforconcatenationletresult=format!("{}{}",s1,s2);println!("......
  • 使用Date获取一个年月日时分秒微秒的时间字符串
    ★目标使用Date获取一个年月日时分秒微秒的时间字符串例如202407021100000100★代码实现constcurrentDate=newDate();constyear=currentDate.getFullYear();constmonth=String(currentDate.getMonth()+1).padStart(2,'0');//月份从0开始,需要加1const......
  • 代码随想录算法训练营第九天|232.用栈实现队列、225.用队列实现栈、 20.有效的括号、1
    文章目录232.用栈实现队列思路--直接模拟225.用队列实现栈解法一、两个队列模拟解法二、一个队列模拟20.有效的括号栈模拟1047.删除字符串中的所有相邻重复项解法一、栈解法二、双指针232.用栈实现队列题目链接:232.用栈实现队列-力扣(LeetCode)题目描述:请你仅......
  • 【LeetCode】反转字符串中的单词
    目录一、题目二、解法完整代码一、题目给你一个字符串s,请你反转字符串中单词的顺序。单词是由非空格字符组成的字符串。s中使用至少一个空格将字符串中的单词分隔开。返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。注意:输入字符串s中可能......
  • 代码随想录算法训练营Day9 | 字符串 151.翻转字符串单词 28.实现strStr() KMP算法介绍
    python中常用:        s[::-1]: 反转整个字符        s.strip():删除开头或结尾处的空白字符     s.split():字符拆分成单词 →list    “”.join(s):list→字符串   (持续更新…) 151.翻转字符串里的单词 题目: Leetcod......
  • 【算法探秘】无重复字符的最长子串:解锁字符串中的独特风景
    【算法探秘】无重复字符的最长子串:解锁字符串中的独特风景一、引言:在字符的海洋中航行二、技术概述:独步字符森林技术定义核心特性代码示例:初尝甜蜜果实三、技术细节:拨开迷雾,洞悉本质原理解析难点剖析四、实战应用:字节跳跃,解密信息应用场景案例展示五、优化与改进:精益......
  • Day8 翻转字符串里面的单词,右旋字符串
    翻转字符串里面的单词我觉得这道题是一道可以很好的帮助我们的理解再次关于快慢双指针,希望我们能够经过我们多次的锻炼来提高自己的水平!题目在知道题里面,我们要做的不仅仅是单纯的翻转字符我们还需要将这个空格整掉,但是在每一个单词与单词之间我们还要有一个空格,所以我们......
  • Day7 反转字符串,反转字符串II,替换数字
    反转字符串 #include<iostream>usingnamespacestd;#include<string>voidfanzhuan(string&s){ for(inti=0,j=s.size()-1;i<s.size()/2;i++,j--) { swap(s[i],s[j]); } cout<<s;}intmain(){ strings; cin>>s; ......