[小专题]另一种字符串匹配的思路——Shift-And算法
- 吐槽:前两天打组队赛遇到一个字符串的题考了这个(见:http://acm.hdu.edu.cn/showproblem.php?pid=5972 )
当时写了个KMP瞎搞然后TLE了(害),赛后去查了许多资料似乎就看见一个题考了这么个鬼东西…
问题给出
- 给一个主串\(S=s_1s_2...s_n\)和一个模式串\(T=(t_{11}|t_{12}|...|t_{1k_1})(t_{21}|t_{22}|...t_{2k_2})...(t_{m1}|t_{m2}|...|t_{mk_m})\),对于\(S\)的一个子串\(S'[1,m]=S[i,i+m-1]\),只要第\(j\)个位置满足\(S[j]=t_{j1},t_{j2},..t_{jk_j}\)中的其中一个,就算匹配成功。找到\(S\)中所有能和\(T\)匹配的子串。
- \(|S|,|T|\)的范围依然很大,\(k_i\)和字符集比较小
问题分析
- 首先想到的是去修改一下KMP里匹配成功的条件…如果在一个位置发现了失配,即\(s[i]\not\subset t[j+1]\),那样我们希望仍然有一个合适的失配数组\(nxt[]\)让我们跳回合适的位置,即找到一个\(T[1,j]\)的最长公共前后缀跳回去,但是这里就出现了问题,如果要保证\(S\)匹配到的后缀要和新的\(T\)的前缀匹配,那就得保证我们找的\(T\)的这个前缀包含了\(T\)的后缀,可是这样就也要改掉\(nxt[]\)数组的求解。
- 好那就改吧,构造失配数组的时候把匹配条件
t[i]==t[j+1]改成包含关系,即我们要让\(T[1,j+1]\)这个前缀包含\(T[i-j,i]\)这个后缀如果\(k_i\)小的话这一步仍然可以认为是\(O(1)\)的时间,整个处理过程仍然是\(O(|T|)\)的。嗯到这里感觉都不错,接下来进行两个字符串的匹配了,\(S\)和\(T\)的比较也改成比较是否包含。如果失配的话同样是\(j=nxt[j]\)地往回跳,一直到\(j==|T|\)匹配成功…到这里似乎都没什么问题 - 但是但是…一旦匹配成功输出结果,\(j\)这个指针该怎么跳?\(j=nxt[j]\)?但是很快就会发现这里这样做会漏掉一些情况…(因为如果要求【跳到的前缀包含了当前的后缀】这样一个苛刻的条件,那样可能会出现\(S\)串马上又可以匹配,但是我们条件太苛刻跳过了的情况)
- 想来想去没法解决这个问题…
- 好吧既然KMP没法解决…不如我们换个算法(逃
另一种字符串匹配方法
和其他算法问题一样,我们可以考虑换一个维护的对象。下标,字符集…
比如这里,我们考虑从另一个角度切入字符串匹配的问题:对于字符集比较小的匹配,对模式串\(T\)里每个字符出现的位置进行记录:即用一个数组\(B[i][j]\)表示字符\(i\)在第\(j\)个位置是否出现。这样记录能够处理这题里令我们头疼的问题:模式串的一个位置允许多种取值。
朴素暴力
好了现在有了这么一个想法,先试试看最暴力地要怎么做这个问题
(约定|S|=m,|T|=n)
\(O(n)\)地求出\(B[][]\)数组
for(int i=1;i<=n;i++)
int k;scanf("%d",&k);
for(int j=1;j<=k;j++)
int t;scanf("%d",&t);
B[t][i]=1
每次暴力匹配\(S\)和\(T\),时间复杂度还是\(O(nm)\)
for(int i=1;i<=m;i++)
int j=1;
for(;j<=n&&B[s[i+j-1]][j];j++);
if(j==n+1)
match!
优化算法
和其他字符串算法的思路一样,我们尝试能不能通过维护一些前后缀的信息来减少信息的冗余:比如这里我们发现,上面的算法每次都在暴力\(O(n)\)地比较\(S[i,i+n-1]\)和\(T[1,n]\),我们可以把这个过程看成\(S[1,i+n-1]\)的后缀和\(T[1,n]\)的前缀进行比较,于是类似KMP的思路,也许我们可以去维护\(S\)的后缀和\(T\)的前缀相关的信息!(这就是Shift-And算法的思路!)
我们考虑再用一个数组\(D[]\)来维护这样一个信息:\(D[j]=1\)当且仅当\(S[i-j+1,i]\)和\(T[1,j]\)匹配,即\(S\)的一个后缀是\(T\)的前缀。否则\(D[j]=0\)。马上我们将会发现用Bool类型储存这样一个信息的优越性。
如果我们让\(i,j\)两个指针一起跑(如图),能写出递推式:\(D[j+1]=(D[j])\&(S[i+1]==T[j+1])\)。进一步我们利用前面做好的数组\(B[][]\),可以把相等的判定修改一下,变成:\(D[j+1]=D[j]\&B[S[i+1]][j+1]\)

到这里都还只是逐位地进行位运算的比较,但是我们注意到这个\(D[]\)似乎可以做成一个\(bitset\),把它看成是一个长度为\(|T|\)的二进制数的话,尝试直接用一个\(D\)表示这个数组,用位运算来实现这个递推。
考虑上面的过程,从\(D[j]\)到\(D[j+1]\)需要先把上一位\(D[j]\)的信息复制过来,再对\(j+1\)位进行一个取\(\&\)的操作,考虑从\(i=1,j=1\)往上递推的整个过程…对于每个\(i\),每次遍历\(1,2,3,...,j...,|T|\),复制信息…对应位置取\(\&\),这个复制信息的过程不就相当于把一个二进制数全部左移一位么?每次取\(\&\)也很麻烦,我们把\(B[i][j]\)的第二个维度也压掉,直接对两个二进制数按位\(\&\),同时为了保证\(\&\)正确性,每次左移完了之后把最低位赋为1。
另外,对于超过\(|T|\)的\(j\)的信息我们可以直接丢掉,所以也不用担心丢失什么信息。
至此,我们已经可以抛去\(j\)这个指针,得到从\(i\)到\(i+1\)递推式:
\(D=(D<<1|1)\&B[S[i+1]]\)实现
核心代码
const int N=5000005;
const int M=1005;
char s[N];
char t[M];
bitset<M>B[10],D;
int n,len;
int main(){
scanf("%d",&n);
rep(i,1,n){
int k;scanf("%d",&k);
rep(j,1,k){
int t;scanf("%d",&t);
B[t].set(i,1);
}
}
scanf("%s",s+1);
len=strlen(s+1);
rep(i,1,len){
D=(D<<1).set(1)&B[s[i]-'0'];
if(D[n]){
char ch=s[i+1];
s[i+1]=0;
puts(s+i-n+1);
s[i+1]=ch;
}
}
return 0;
}
参考资料
- https://www.cnblogs.com/longdouhzt/archive/2011/09/25/2190550.html
- https://blog.csdn.net/silence401/article/details/77446537
- http://en.volupedia.org/wiki/Bitap_algorithm
[小专题]另一种字符串匹配的思路——Shift-And算法的更多相关文章
- 快速字符串匹配一: 看毛片算法(KMP)
前言 由于需要做一个快速匹配敏感关键词的服务,为了提供一个高效,准确,低能耗的关键词匹配服务,我进行了漫长的探索.这里把过程记录成系列博客,供大家参考. 在一开始,接收到快速敏感词匹配时,我就想到了 ...
- 字符串匹配-BF算法和KMP算法
声明:图片及内容基于https://www.bilibili.com/video/av95949609 BF算法 原理分析 Brute Force 暴力算法 用来在主串中查找模式串是否存以及出现位置 ...
- Boyer-Moore(BM)算法,文本查找,字符串匹配问题
KMP算法的时间复杂度是O(m + n),而Boyer-Moore算法的时间复杂度是O(n/m).文本查找中“ctrl + f”一般就是采用的BM算法. Boyer-Moore算法的关键点: 从右遍历 ...
- 【模板】字符串匹配的三种做法(Hash、KMP、STL)
题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 输入输出格式 输入格式: 第一行为一个字符串,即为s1 第二行为一个字符串,即为s2 输出格式: 1行 ...
- sdut 2125串结构练习--字符串匹配【两种KMP算法】
串结构练习——字符串匹配 Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^ 题目链接:http://acm.sdut.edu.cn/sduto ...
- KMP(字符串匹配)
1.KMP是一种用来进行字符串匹配的算法,首先我们来看一下普通的匹配算法: 现在我们要在字符串ababcabcacbab中找abcac是不是存在,那么传统的查找方法就是一个个的匹配了,如图: 经过六趟 ...
- 字符串匹配KMP算法的讲解C++
转自http://blog.csdn.net/starstar1992/article/details/54913261 也可以参考http://blog.csdn.net/liu940204/art ...
- 字符串匹配KMP算法详解
1. 引言 以前看过很多次KMP算法,一直觉得很有用,但都没有搞明白,一方面是网上很少有比较详细的通俗易懂的讲解,另一方面也怪自己没有沉下心来研究.最近在leetcode上又遇见字符串匹配的题目,以此 ...
- 字符串匹配-KMP
节选自 https://www.cnblogs.com/zhangtianq/p/5839909.html 字符串匹配 KMP O(m+n) O原来的暴力算法 当不匹配的时候 尽管之前文本串和模式串已 ...
随机推荐
- 关于Java集合框架,这篇讲的还算不错了,建议大家看看!
集合框架 为什么要用集合而非数组: 虽然数组是保存一组对象最有效的方式,但是数组具有固定尺寸,但在写程序时并不知道将需要多少个对象.而集合能够自动地调整自己的尺寸. 打印数组时,必须使用Arrays. ...
- python应用(2):写个python程序给自己用
用python写一个程序,然后在命令行上执行,看不到界面(UI),这种程序很常见了,叫命令行程序.然而很多人,特别是不懂程序的人,更需要看到的是一个有界面的,能通过鼠标操作的程序,毕竟已经迈进&quo ...
- 【操作系统】先来先服务和短作业优先算法(C语言实现)
[操作系统] 先来先服务算法和短作业优先算法实现 介绍: 1.先来先服务 (FCFS: first come first service) 如果早就绪的进程排在就绪队列的前面,迟就绪的进程排在就绪队列 ...
- Bootstrap Blazor 组件介绍 Table (一)自动生成列功能介绍
Bootstrap Blazor 是一套企业级 UI 组件库,适配移动端支持各种主流浏览器,已经在多个交付项目中使用.通过本套组件可以大大缩短开发周期,节约开发成本.目前已经开发.封装了 70 多个组 ...
- 数论之prufer序列
定义 \(Prufer\) 数列是无根树的一种数列. 在组合数学中,\(Prufer\) 数列由有一个对于顶点标过号的树转化来的数列,点数为 \(n\) 的树转化来的 \(Prufer\) 数列长度为 ...
- [题解] [USACO05JAN]Muddy Fields G
题目TP门 题目大意 在一个 \(R×C\) 的矩阵中,每个点有两个状态:草地和泥地.你需要在泥地里铺 \(1×k\) 木块, \(k\) 为任意整数,求最少要多少木块. 思路 两个横向木块不会互相干 ...
- fist-第七天冲刺随笔
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...
- Netty 搭建 WebSocket 服务端
一.编码器.解码器 ... ... @Autowired private HttpRequestHandler httpRequestHandler; @Autowired private TextW ...
- WPF 学习笔记(一)
一.概述 WPF(Windows Presentation Foundation)是微软推出的基于Windows 的用户界面框架,随着.NET Framework 3.0发布第一个版本.它提供了统一的 ...
- day2(APlview+Serializers使用)
1.APIview使用 ModelVIewSet 是对 APIView 封装 ModelSerializer是对Serializeer 1.1 在user/urls.py中添加路由 urlpatte ...