[除草]字符串专题

最近两个星期都在刷字符串,感觉对模板倒是熟练了不少,但是最关键的一步——想出题的算法感觉还是不行啊…………瞬间有种被屠得渣都不剩的感觉,但求省选时别跪。总而言之,字符串的题就要告一段落了,接下来就来总结一下吧。

 

之前做的题就不贴出来了,只发最近两周做的吧。代码懒得贴了,我写得无比丑陋的代码没有任何参考价值。

 

首先是模板级的,没有任何思维难度的题…………  

 

hdu1711 Number Sequence:

裸的单串匹配。我写了KMP和Z-Box,加了读入优化后达到了171ms,纯粹拿来测试模板速度的……各种srO CRF的140ms Orz啊…………

ps 只要你闲的蛋疼这道题可以哈希的……也不难写……

hdu2222 Keywords Search:

裸的多串匹配。也是纯粹模板测速用的。使用AC自动机即可。

ps 这道题卡内存无限恶心,如果开静态内存的话节点数要刚好274000才行,少一点要RE,多一点要MLE,我简直对它无语了……当然如果你是动态内存当我什么也没说。

hdu2896 病毒侵袭:

给一些模式串,问它们在分别哪些模板串中出现过。还是裸的AC自动机,只不过需要在单词标记处挂一根链表来回答询问,注意判重。

hdu3065 病毒侵袭持续中:

上一题的简化版,不用判重了……

UVALive4657 Top 10:

给一些单词,有10^5个查询,每次询问一个串是字典中哪些单词的子串,输出满足要求的单词排序后(关键字顺序为长度,字典序,读入编号)的前10个(不足10个则有几个就输出几个)。

乍一看没法解决,其实发现如果倒过来做,即把询问离线,建成AC自动机,然后拿字典里面的单词进行匹配。这样问题就转化成了裸的匹配模型了。至于要求输出前10个,就把单词先排好序,按顺序匹配,如果对某个询问已经有10个答案了就不将当前单词加入即可。

poj1743 Musical Theme:

男人八题之一,也是论文题——求一个串中重复至少两次并且不重叠(即这两个重复子串没有公共部分),且相邻元素差值相同的最长子串。

把差值求出来后跑一遍后缀数组。二分长度k,每次把height值>=k后缀的分为一组,然后找每一组里面的两个后缀起始位置的最大差值是否大于k,如果有那么这两个后缀的最长公共前缀就是一个可行解。

ps 差值可能为负,处理方法是把它们全部加上一个数。

poj3261 Milk Patterns:

求一个串中重复至少k次的子串(这k次重复可以重叠)

论文题。后缀数组,二分长度l,将height>=l的分为一组,判断是否有一组包含大于等于k个后缀即可。

poj2774 Long Long Message:

求两个串的最长公共子串。

论文题。连起来后缀数组,枚举排名相邻的两个后缀,看它们是否属于两个不同的串,如果是就用它们的height更新答案。

ps 这道题可以哈希的………………

 

接下来是一些稍有技巧性的题,只要想到了就和模板没什么差别。

 

poj2406 Power Strings:

已知一个串是由若干个短串重复出现得到的,求这个满足要求的最短的短串长度(即重复次数最多)。

根据连续重复子串的性质,如果循环节长度为l,则lcp(1,1+l)>=n-l+1。根据这个性质可以直接一遍z-box判断出合法长度。

ps 上面的性质还可以扩展到如果循环节长度为l,循环次数为r,则该串前l*(r-1)位与后l*(r-1)位一定相同,所以用KMP也可以搞定。

ps ps 当然如果你足够蛋疼,后缀数组也可以解决,只是RMQ只用处理suffix(1)与其它后缀的lcp,否则你要MLE……

poj2185 Miliking Grid:

上面那道题的扩展版,从一维变成二维,并且并不要求重复恰好k次(即最后一次可以不完整重复)。

解法类似,行做一遍列做一遍,取最小公倍数即可。

hdu4468 Spy:

给定一个串A,已知它是由若干个串B的前缀与串B本身拼成的,求最短的符合要求的串B。

由于它是由若干前缀构成的,所以可以想到构造一个这样的串(这一步我想了好久,但是WZY神牛瞬秒了 在此srO srO srO WZY Orz Orz Orz )。初始化答案长度为空串,每次把目标串当前位尝试与当前答案串进行匹配,如果匹配失败(即目标串当前位与答案串的匹配长度为0)就把答案串上次在目标串中完整出现的结尾位置到当前位置的所有字符加入答案串中;如果匹配了整个答案串就更新答案串在目标串中最近的出现位置。匹配完目标串后检查是否完全匹配答案串,如果否,就把上一次答案串出现的结尾位置到目标串尾的所有字符都加入答案串。匹配的时候用KMP就可以了。

本题构造的思路非常巧妙,关键在于要抓住“它们都是答案串的前缀”的性质,并联想到匹配模型和构造求解。能想到这一点,本题应该就水了(所以给秒想的WZY神牛跪烂)。

spoj687 Repeats:

求一个串中重复次数最多的连续重复字串的重复次数。

根据连续重复子串的性质,还是有r*l>=lcp(x,x+l)>=(r-1)*l,(l为循环节长度,r为循环次数,x为起始位置)。如果我们枚举x且枚举l,那么就是O(n^2)的复杂度。但是我们发现x与x+l必定同时穿过1*l+1,2*l+1,...中相邻的两个。因此我们枚举长度l时,起点只需要枚举l的倍数+1(1下标)的点,再看它们向前向后各能匹配多远即可,这样时间复杂度变成了O(nlogn)。至于如何找到向前能匹配多远,可以采取倒着做一遍后缀数组,也可以根据向后匹配的长度mod l的余数来确定向前多少位才可能对答案有贡献(比如向后匹配的位数mod l为k,那么只有向前l-k位才可能对答案有贡献,因为少了即使匹配上也不会使答案+1,多了再怎么匹配答案也最多+1),然后就可以根据lcp求循环次数了。

poj3693 Maximum repetition substring:

和上一题差不多,只是要求输出字典序最小的满足要求的解。解决方案是将所有重复次数最多的循环节长度全部记下来(可以证明最多sqrt(n)个符合条件的长度),然后按照sa数组的顺序枚举起始位置依次检查各个长度的该循环次数的循环串是否存在,如果存在就立即输解。

本题最巧妙的地方在于按照sa数组的顺序枚举,这样就能保证字典序最小;同时能想到重复次数最多的循环节长度不超过sqrt(n)也非常重要。这需要对后缀数组非常熟悉才行,看来以后真的还得再做很多题……

ps 这道题姿势正确的话暴力也能过………………………………………………无语啊………………………………

 

最后是字符串与其它数据结构或算法的结合,也算是字符串各算法的扩展吧。

 

poj3415 Common Substrings:

求两个字符串长度大于等于k的公共子串个数。

最难的论文题。后缀数组后按sa遍历后缀,每发现一个B的后缀就计算它与之前的A的后缀的lcp之和(因为两个lcp为h的后缀对答案的贡献将是h-k+1),对A同样如此。由于lcp是一段区间的最小值,可以通过单调栈来快速维护当前串和之前的lcp。

维护两个值sum_a,sum_b分别代表当前后缀与属于之前A串,B串的后缀的lcp值之和。每次向单调栈中加入相邻两个串的height值,如果新加入的height大于栈顶的height就边弹栈边从维护的和中扣除多的lcp值,注意分属于两个串的后缀要分开计算。那么对每个后缀操作完后得到的当前后缀与之前与自己不在同一个串中的后缀的lcp之和(即维护的值)的最大值就是答案。

ps 这道题说起太绕了,细节还是要自己想…………

bzoj1009 GT考试:

求长度为l的数字串中,不包含某个特定串的串有多少个(MOD一个值)。 

KMP+DP+矩阵快速幂。dp[i][j]表示长度为i,已经和特定串匹配了j位的串有多少个,那么很明显状态之间的转移可以通过KMP得到。

由于l太大,须将转移关系建成矩阵,然后用矩阵快速幂即可。

poj2778 DNA Sequence:

和上面那道题一样,只是不能包含的串不止一个。dp[i][j]表示长度为i,当前匹配位置是AC自动机的第j个节点,将转移关系通过AC自动机建立后就和上题一样了。

poj3691 DNA Repair:

给一个串,要求将它变成不包含任何病毒串的一个串,每改变一个字符花费1的代价,求最小花费。

dp[i][j]表示长度为i,当前匹配位置是AC自动机的第j个节点的最小花费(其实AC自动机上的DP都长这样,可以说是模板了……)

将AC自动机补全成Trie图,沿着Trie图上的边转移,如果这条边是原来有的(即不改变),并且转移到合法状态(没有病毒串),那么就没有花费;否则有增加1的花费。

hdu4057 Rescue the Rabbit:

给若干(小于10)个串,每个串有一个价值(可正可负),求构造一个指定长度的长串使它价值最大,长串的价值定义为包含的所有给定的串的价值和(出现多次只算一次)。

AC自动机+状压DP。容易想到用dp[i][j][k]表示长度为i,匹配到j号节点,状态为k的最大价值,但这样会T。稍加思考应该会发现可以根据状态来计算价值,因此将dp[i][j][k]定义为布尔表示该状态是否能达到,转移时采取或操作,然后就可以轻松跑过了。

ps 什么?空间被卡了??哈哈,你实现得不够优秀,换滚动数组吧……

bzoj2434 阿狸的打字机:

给n个串,求第x个串在第y个串中出现了多少次。

第一次知道AC自动机不仅可以处理匹配问题,还可以处理子串问题。将AC自动机fail指针反向后得到一棵fail树,它和Trie刚好相反,每个节点对应的串一定是它子树节点对应的串中的后缀(Trie则是前缀)。那么,对于一个询问(x,y),其实就是求y的前缀中有多少个恰好以x为后缀(或者反过来,一样的),也就是说,求在Trie树上从y对应节点到根的这条路径上,有多少个点属于fail树上x的子树。那么我们可以离线询问后,对Trie树dfs,则当前所在点只包含从当前点到Trie的根的信息,而关于求多少个节点属于fail树的子树的问题可以通过fail树的dfs序+树状数组搞定,然后这道题就解决了。

ps 有没有在线做法呢?其实可以把Trie树上各节点的信息搞成主席树,然后就可以支持在线了,是不是很优美??

bzoj2746 旅行问题:

给n个串,求第i个串的前j位和第k个串的前l位的最长公共后缀,并满足这个后缀同时是某个串的前缀。

由于是后缀问题,很明显是在fail树上求LCA的问题。不过这道题由于数据太弱,可以采取哈希乱搞大法骗过。在此srO srO LYD大神 Orz Orz,我学习了他的乱搞大法后,轻松rank1…………

bzoj2754 喵星球上的点名:

多模式串在多目标串中的匹配问题,要求对每一个模式串输出匹配上的目标串数,对每一个目标串输出匹配上的模式串数。

去年的省选题,当时我靠裸骗了30分…………看起来就是一道模板题,但超大的字符集,超大的数据范围使得这道题不是一般的麻烦,AC自动机被卡空间,需要用平衡树(或者map,set)维护Trie树的儿子域。所幸的是,本题采取这种方式后裸的AC自动机匹配,以及后缀数组后暴力都能够通过,这里是我初学AC自动机时写的一篇关于裸过这道题的文章:http://www.cnblogs.com/stickjitb/archive/2012/05/27/2520281.html

但是很显然我们不能满足于裸过。正解的一种方式是建立fail树的dfs序,然后每次相当于询问一棵子树里面有多少个点(即子树里点的权值和),以及每个有权值的点被多少个不同的区间覆盖过。两者都可以通过树状数组来实现。另一种方式是通过后缀数组排序后,二分每个与模式串起点的后缀的lcp大于等于对应模式串长度的一段后缀的左右端点,然后相当于统计一段区间内有多少个不同的数和每个数被覆盖了多少次,也可以转化成树状数组来实现。但是,鉴于这两种方法太过麻烦,我没写,而且我相信,考场上也不会有人蛋疼到写这种神题的地步吧(非正解但可A的算法除外,骗分是王道啊)…………………………

 

总结:都说字符串水,其实是因为大部分精妙的东西都被模板封装好了,考察的是我们对模板的熟练运用的能力。所以说真正考察的不是算法本身,而是思维。字符串各算法都非常精妙,虽然说弄着有点绕,但是倘若解决,定是比那些模拟题优美多了。总之,近乎模板的题目在OI中是不存在的,不论什么东西,还是必须考多刷题练习思维能力,想办法绕过那些弯找到解法,才能叫真正刷翻了。

posted on 2013-03-23 21:00  stickjitb  阅读(377)  评论(2编辑  收藏  举报