标题是骗你进来的,其实里面全是题目。
最近一直在搞字符串......
把一些有表明性或者有必定难度的题放在这里作一个总结。算法
给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,
每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l..r]\)中的哪一个串里的出现次数最多,并输出出现次数。
若有多解输出最靠前的那一个。数据范围:\(|S|,\sum |T|,q\leq 10^5\)。数组
对\(T[1...m]\)创建广义\(SAM\),用线段树合并维护出现次数。
每次查询先倍增到相应结点,而后直接线段树区间查询。spa
给定一个打字机,有加字符、删字符、打印三种操做。
给定操做序列\(S\),而后有\(Q\)次询问,每次回答第\(x\)次打印的串在第\(y\)次打印的串中出现几回。
数据范围:\(|S|,Q\leq 10^5\)指针
用栈模拟建出全部打印串的\(Trie\)树,而后构建\(AC\)自动机与\(fail\)树。
每次询问至关于问\(x\)到\(Trie\)的根这条路径上有多少个点可以跳\(fail\)跳到\(y\)。
对\(Trie\)进行\(dfs\),用树状数组维护\(fail\)树的子树和。调试
给定一个串\(S\),求每个前缀的不相交\(border\)数。 数据范围:\(|S|\leq 10^7\)。code
解法一:建出\(fail\)树,而后\(dfs\)一遍\(fail\)树,用单调队列维护合法前缀大小。
解法二:先预处理出\(next\)数组,而后相似\(next\)数组的求法求答案,当不合法时跳\(next\)数组。排序
给定一个串的每一个回文中心的扩展大小,构造知足条件的最小字典序串。数据范围:\(n\leq 10^5\)队列
对这个串跑一遍\(manacher\),直接模拟便可,用并查集维护两个位置的字符是否相同。
最后使用最小表示法求出答案串。字符串
给定一个串\(S\),求\(\sum_{i} \sum_{i\neq j} lcp(suf_i,suf_j)\)。数据范围\(|S|\leq 10^5\)。string
使用后缀数组,按照\(Height\)排序后合并后缀,用带权并查集维护答案。
或者建出\(SAM\)的\(fail\)树后,直接\(dfs\)一遍\(fail\)树,每次直接合并子树答案。
求一个最短的模板串\(T\),使得用\(T\)对长度为\(n\)的串反复染色后能够获得\(S\)。
注意:同一个位置能够屡次染色,但只能染一种颜色。
数据范围:\(n\leq 5*10^5\)。
显然\(T\)是\(S\)的一个\(border\)。
那么限制条件就是\(T\)在串\(S\)中的出现位置的最大间距不能超过\(|T|\),其中\(T\)为一个\(border\)。
注意到\(border\)匹配的特殊性,当一个前缀\(pre\)的\(border\)为\(T\)时,\(T\)就在该点匹配上了一次。
因此跑\(kmp\)后建出\(fail\)树。
对于一个\(T\),它合法的充要条件为子树内的点之间的间距不超过\(|T|\),直接用\(set\)维护最大间距。
给一个串\(S\),把它划分为最多\(K\)段,
而后挑选出全部段中的最大字典序串,再把这些串取最大字典序串,称获得的串为\(T\)。
最小化\(T\)的字典序并输出\(T\)。 数据范围:\(|S|\leq 3*10^5\)、\(K\leq 20\) 。
最大最小化问题,首先二分\(T\)在\(S\)的全部子串中的排名。
而后贪心验证。因为咱们的后缀数组是之后缀排名的,因此从后往前扫。
若当前的后缀排名小于等于\(T\),则直接跳过。
不然须要考虑割一刀,先求\(lcp\),而后查看\(lcp\)长度范围内是否割了一刀,若是没有就割一刀。
最后比较割的次数与\(K\)的关系便可。
须要实现的功能有快速求\(lcp\),给串求排名,给排名求串,这些均可以用\(SA\)解决。
给一个串\(S\),求把每个位置替换为特殊字符#后本质不一样的子串个数。
数据范围:\(|S|\leq 10^5\)。
先构建\(SAM\),把原串本质不一样的子串个数求出来,而后考虑变化量。
首先增长量很显然是\(i(n-i+1)\) ,考虑求减小量。
对于\(SAM\)上的每个点,咱们维护其最大\(endpos\)与最小\(endpos\)。
而后做图能够发现,这个结点对两个区间的贡献分别为 等差数列 和 一个常数。
差分便可。
给定两个排列\(S,T\),要求\(T\)在\(S\)中匹配了多少次。
这里的匹配定义为:相对大小相同即算匹配上。
数据范围:\(|S|,|T|\leq 10^6\) 。空间限制:\(64MB\)。
魔改一下\(kmp\)算法。
对于\(T\),咱们能够求出一个\(next\)数组,表示失配后到达位置,而后在\(S\)上相似的跑\(kmp\)便可。
如今的问题变为:求一个区间内大于某个数的个数,直接想法是主席树,但会\(MLE\)。
深度发掘一下\(kmp\)的原理,发现它其实相似一个双指针。
因此使用树状数组,在跳\(next\)的时候暴力把删除元素在树状数组中删掉。
给定一个偶数长度的串\(S\),试着把它划分为偶数段。
设划分为了\(k\)段,那么须要知足\(s_i=s_{k-i+1}\),求方案数。数据范围:\(|S|\leq 10^6\)
首先构建串\(S'= s_1s_ns_2s_{n-1}...\),问题转化为把\(S'\)划分为若干偶数回文串的方案数。
对于一个回文串,
若它的若干回文后缀都不超过其长度的一半,则这些回文后缀必定构成等差数列。
这样的话,回文树上的某个结点的全部祖先最多只会构成\(log\)个等差数列。
咱们考虑让同一等差数列中的点一块儿转移,维护\(up\)表示最浅的非等差位置。
对于同一等差数列中的点,因为其长度不超过原串长度的一半,
故咱们对称后恰好只有\(up\)处的贡献没有加上,因此暴力跳等差数列的同时把该贡献加上便可。
给定一个串\(S\),有\(Q\)次询问,每次给定一个串\(T\),求不在\(S[l,r]\)中出现的\(T\)的子串个数。
数据范围:\(|S|,Q,\sum |T| \leq 5 * 10^5\),其中\(l,r\)也是每次询问给定的变量。
对\(S\)创建后缀自动机,用线段树合并获得其\(endpos\)集合。
对于每次询问,先对\(T\)创建后缀自动机,求的\(T\)的子串个数,而后再把在\(S\)中出现的子串减掉。
对于\(T\),咱们求出其每一个前缀的最长匹配后缀\(lim\)。
那么对于\(T\)的\(SAM\)上的每一个点,利用\(lim\)把非法贡献减掉便可。
考虑求\(lim\),直接把\(T\)放在\(S\)的\(SAM\)上作匹配,经过线段树查询\(endpos\)判断是否出现。
当失配的时候,不能直接跳\(fail\),而应该要使长度减一,而后再次检查。
能够发现这个过程当中,\(T\)和匹配长度\(len\)的关系相似一个双指针,因此复杂度是没有问题的。
给定一个串\(S\),求全部子串划分为\(AABB\)形式的方案数之和。数据范围:\(|S|\leq 10^5\)。
惊现\(NOI\)史上最良心出题人,白送\(95\)分简直搞笑。考虑最后\(5\)分应该怎么拿。
显然\(AABB\)是一个障眼法,咱们其实只用求\(AA\)的划分方案就好了。
开始构造,枚举一下\(A\)的长度\(len\)。
而后对于\(S\),每一个长度\(len\)咱们就设置一个顶标点\(p_i\)。
那么对于任意\(|A|=len\)的\(AA\)串,它必定刚好通过两个顶标点。
因此对于相邻两个顶标点,求一下它们的最长公共前、后缀,而后算一下贡献便可。
给定一个串\(S\)和一棵\(n\)个结点树,其中树上的每个结点有一个字符。
求树上每一条有序路径构成的字符串在\(S\)中的出现次数之和。数据范围:\(n,|S|\leq 50000\)。
树上路径问题考虑点分治,创建点分树。因为\(n\)范围比较小,因此能够数据均摊分治。
对于点分子树大小\(\leq \sqrt{n}\)的点,咱们直接暴力作,复杂度\(O(\sqrt{n}^3) = O(n\sqrt{n})\)。
对于点分子树大小\(> \sqrt{n}\),这类点显然只有\(\sqrt{n}\)个,咱们尝试用其它理论解决。
考虑获得了两条路径,而后把它们拼接在一块儿。
那么对应到字符串上,就是一个前缀和一个后缀进行拼接。
因此咱们只须要知道每个前缀的方案数和每个后缀的方案数,而后再乘一下就好了。
咱们以求前缀方案数为例,后缀方案数反过来作便可。
观察一下匹配过程,咱们肯定了一个\(endpos\)字符,而后须要向前作匹配。
这显然是\(SAM\)的\(DAG\)和\(fail\)树难以作到的。
因此咱们须要将\(SAM\)生成的\(fail\)树进一步处理,获得其前缀树,而后匹配就是跳前缀树的子树。
最后的问题在于如何快速给匹配的点的全部\(endpos\)加上贡献。
回顾一下\(endpos\)集合的获得方法,不难发现只须要把这个过程给逆过来就好了。
咱们先在当前点打上标记,所有匹配完后,顺着\(fail\)树把标记向下推送。
那么对于一个前缀,它在\(fail\)树上对应的结点必定是一个叶子结点,咱们直接在该叶子查答案。
给定一个串\(S\),
有\(Q\)次询问,每次求把串划分为非空三段,且三段中至少包含一个\(S[pl,pr]\)的方案数。
数据范围:\(|S|,|Q|\leq 100000\),其中\(pl,pr\)为每次给定的变量。
此题代码细节贼多,若是要写请务必作好代码调试至少一个晚上的准备。
正难则反,考虑求不包含\(S[pl,pr]\)的方案数。
为了书写方便咱们令\(T = S[pl,pr]\),同时定义\(T\)在\(S\)中的出现次数为\(n\),
定义\(T\)在\(S\)中的出现串为\(p_1,p_2...p_n\),其中\(l_{p_i},r_{p_i}\)表示它们的左右端点。
令\(mn = p_1,mx = p_n\),令\(len = pr-pl+1\)。
咱们如今的目标就是用两刀切掉\(S\)中出现的全部\(T\)。
大力讨论:
( 1 ) 若存在三个不相交的\(T\),此时显然无解。
( 2 ) 不然,若\(r_{mn} > l_{mx}\),即存在一刀流切法。
( 3 ) 不然,即\(r_{mn} \leq l_{mx}\),即不存在一刀流切法。
顺着上一种思路,依旧考虑枚举左边那刀切掉了哪些\(T\)。
那么有:\(Ans = \sum_{i} (r_{p_{i+1}} - r_{p_i}) (r_{mn} - l_{p_{i+1}})\) 。
可是因为不存在一刀流,因此必定会有一个\(i\)被\(r_{mn}\)限制,致使切割范围不是完整区间。
因此咱们把左端点最靠近\(r_{mn}\)的那个点先丢掉,设其为\(p_t\),
而后就能够获得一个关于\(i\)的限制条件:\(r_{i+1} > l_{mx}\)、\(l_i < r_{mn}\)。
这里须要注意一个天坑(大家就使劲感谢我吧):
咱们找到的是最小的符合条件的\(r_{i+1}\),而计算区间端点应该是\(r_i\),因此这里须要找一次前趋。
而后咱们再把以前丢掉的那个点\(p_{t}\)给捡回来,它的答案应该是\((r_{mn}-l_{p_t})(r_{p_{t+1}} - l_{mx})\)。
QaQ
累死我了,要是上面哪里写错了麻烦各位吱一声。
到此为止咱们已经讨论了全部状况。
惟一的问题在于如何实现,
能够注意到上面全部式子中与\(endpos\)有关的只有\(\sum r_i(r_{i+1}-r_i),\sum r_i\)。
因此彻底能够直接使用线段树维护区间最大、最小值,进而使得这两个信息能够合并。
同时咱们发现,维护区间最大、最小值更是一并解决了找前趋、后继的问题。
因此咱们对\(S\)建\(SAM\),把询问离线,而后按拓扑序逆序进行线段树合并,做出相应回答便可。 至此问题终于解决。