zoukankan      html  css  js  c++  java
  • 简单字符串

    最小循环表示法

    求一个字符串的最小循环表示法的起始位置。

    先把串倍长,维护两个指针(i,j)表示可能的最小循环表示的起始位置。
    暴力求出(k=lcp(i,j)),然后比较(s_{i+k},s_{j+k})
    如果(s_{i+k}<s_{j+k}),那么([j,j+k])范围内的所有下标均不可能成为最小循环表示的起始位置,所以令(j=j+k+1)
    如果(s_{j+k}<s_{i+k}),那么([i,i+k])范围内的所有下标均不可能成为最小循环表示的起始位置,所以令(i=i+k+1)
    直到(i,j)有一个超过了(n)为止。
    我们暴力求一次(k=lcp(i,j))的复杂度是(O(k))的,而(i,j)有一个会增加(k),因此时间复杂度是均摊(O(n))的。

    Manacher算法

    求出一个字符串中以各个位置为中心的最长回文子串的长度。

    为了解决回文中心在两个字符中间的问题,我们在相邻两个字符间都插入一个$
    (len_i)表示以(i)为中心的最长回文子串的半径((i)自己算(1)),同时记录前缀中(i+len_i)的最大值(mx)与其下标(pos)
    考虑从左往右计算(len),假如当前的位置是(i),那么根据对称性我们知道(len_igemin(len_{pos*2-i},mx-i)),然后我们再暴力扩展(len_i)。这样我们就可以求出(len_i)了。
    因为每次成功的暴力扩展一定会使(mx)向右移动,因此时间复杂度是均摊(O(n))的。

    Knuth-Morris-Pratt算法

    求出字符串(s)(next)数组,(next_i)表示(pre_i)的最长border的长度。

    考虑从左往右计算(next),假如当前的位置是(i),我们先令(k=next_{i-1}),然后判断(s_{k+1},s_i)是否相等,相等就有(next_i=k+1),否则就令(k=next_k)继续跳,直到相等或者跳到(0)为止。
    因为一个位置的(next)被跳过之后就不会再被跳了,因此时间复杂度是均摊(O(n))的。

    Some Applications

    (1.)给定(s,t),求出(t)(s)中的出现次数以及出现位置。

    把暴力匹配时的往后跳(1)改成跳失配位置的(next)就行了,复杂度是均摊(O(n))的。

    (2.)给定(s,t),求出(s)的每个(pre)(t)中的出现次数。

    先考虑(t=s)的情况,直接做似乎不太好做,我们考虑计算以每个位置(i)结尾有哪些(pre)出现过。
    根据KMP的过程我们发现就是(i,next_i,next_{next_i-1},cdots)这些(pre)
    (i)我们单独抠出来最后算,其它的东西构成了一个树型结构,即(pre_k)出现的地方(pre_{next_k-1})一定会出现。
    我们知道每个(pre_{next_i})都至少出现了一次,然后我们再自底向上(实际上就是从后往前)做个后缀和就好了。
    然后考虑(t e s)怎么做。先求出(s)的每个(pre)(s)中的出现次数,再求出(s)的每个(pre)s$t中的出现次数,然后减一下就好了。这个过程的含义已经不言而喻了。
    很显然,时间复杂度都是(O(n))

    Z-算法

    给定字符串(s),求(s)(t)每个(suf)(lcp)

    先考虑(t=s)的情况,记(len_i)表示以(lcp(s,suf_i)),同时记录前缀中(i+len_i)的最大值(mx)与其下标(pos)
    考虑从左往右计算(len),假如当前的位置是(i),那么我们知道(len_igemin(len_{i-pos+1},mx-i)),然后我们再暴力扩展(len_i)。这样我们就可以求出(len_i)了。
    因为每次成功的暴力扩展一定会使(mx)向右移动,因此时间复杂度是均摊(O(n))的。
    然后考虑(t e s)怎么做。求出(s)s$t每个(suf)(lcp)就好了。

    SA

    求出(s)的每个(suf)的相对大小关系。

    (rank_i)表示(suf_i)的排名,(sa_i)表示排名为(i)(suf)的起始位置。
    SA-IS

    (height)数组

    定义(h_i=lcp(suf_{sa_i},suf_{sa_{i-1}}),h_1=0)
    我们可以得到一个引理:(h_{rank_i}ge h_{rank_{i-1}}-1)
    因此我们可以暴力扩展求出(h),复杂度为均摊(O(n))

    Some Applications

    (1.)给定(s),求最小的从(s)首尾取字符得到的新串

    如果首尾字符不同那么可以简单贪心。
    如果相同的话我们比较当前串的顺序和逆序(即原串的某个(suf)(pre)),然后根据结果选择。
    因此我们可以求s$t(t)(s)的逆序)的SA,这样就可以比较(s)的任意两个前后缀的大小了。
    时间复杂度(O(n))

    (2.)给定(s),求(lcp(suf_i,suf_j))

    (lcp(suf_{sa_i},suf_{sa_j})=minlimits_{k=i+1}^jh_k)
    利用(O(n)-O(1))RMQ可以做到(O(n)-O(1))

    (3.)给定(s),比较(s_{l,r})(s_{L,R})

    (lcp(suf_l,suf_r)gemin(r-l+1,R-L+1)),那么说明一个串包含于另一个串,大小关系就是(r-l+1)(R-L+1)的关系。否则直接比较(rank_l,rand_L)即可。
    时间复杂度(O(n)-O(1))

    (4.)给定字符串(s),求本质不同的子串个数

    考虑简单容斥,答案等于总数减重复数,即({n+1choose 2}-sumlimits_{i=1}^nh_i)
    时间复杂度(O(n))

    (5.)给定(s),求至少出现(k)次的最长子串

    出现至少(k)次的子串就是(k)(suf)(lcp),显然取(rank)连续的一段是最优的。那么单调队列随便搞搞就完事了。
    时间复杂度(O(n))

    (6.)给定(s),求不重叠的最长重复子串

    先二分答案(k),我们知道(lcp(sa_i,sa_j)=minlimits_{k=i+1}^jh_k),因此我们把(sa)分成若干个连续段,满足对于一段([l,r]),有(forall iin(l,r],h_ige k)。这样每个连续段中的后缀之间的(lcp)就会(ge k)了,我们只需要查询是否存在某个连续段满足(maxlimits_{k=l}^r sa_k-minlimits_{k=l}^r sa_kge k)即可。
    利用(O(n)-O(1))RMQ可以做到(O(nlog n))

    (7.)给定字符串(s),已知(s=t^r(rinmathbb Q)),求最大(r)

    枚举(t)的长度(l),先判断是否有(lmid n),然后是否有(lcp(suf_1,suf_{l+1})=n-l)
    预处理出(h)数组中任意一个位置到(h_{rank_1})的最小值即可。
    时间复杂度(O(n))

    (8.)给定(s),求(s)的子串中可以表示成(t^r(rin Q))的最大(r)以及对应的(t)

    枚举(t)的长度(l),首先(t)至少会出现一次,所以我们只考虑(rge2)的情况。
    很显然这个子串至少会包含(s_l,s_{2l},s_{3l},cdots)中相邻的两个。
    那么我们看(s_{il},s_{(i+1)l})能够向前向后匹配多远,如果匹配的长度为(m),这里的重复次数就是(r=lfloorfrac ml floor+1)
    相当于我们需要支持查询两个(suf)(lcp)和两个(pre)(lcs),正序倒序跑两边SA然后(O(n)-O(1))RMQ即可。
    时间复杂度(O(nlog n))。实际上可以用Lyndon Array做到(O(n)),这里就不讲了。

    (9.)给定(s,t),求(s,t)(lcs)(最长公共子串)

    s$t的SA,然后枚举(h_i),若(sa_{i-1},s_i)分别在s部分和t部分,那么(h_i)就是一个可能的(lcs),把所有可能的答案取个(max)就好了。
    时间复杂度(O(n))

    (10.)给定(s,t),求长度(ge k)(cs)(公共子串)个数

    这里的(cs)定义为(s_{l,r}=t_{L,R})((l,r,L,R))四元组。
    先求s$t的SA,那么我们要求的就是每个在t部分的(sa_i)只经过(ge k)(h_j)能够到达多少在s部分的(sa_i)。开两个单调栈随便搞搞就完事了。
    时间复杂度(O(n))

    (11.)给定(m)个字符串,求在至少(k)个字符串中出现的最长的串

    先把所有串连起来,中间插一个$得到(t)并求(t)的SA。
    然后我们二分一下答案(r),像例题(6)中的做法一样把(sa)分成若干个连续段,然后判断是否有某一段中包含至少(k)个字符串部分的(suf),开个桶随便搞搞就完事了。
    时间复杂度(O((sum n)log(sum n)))

    (12.)给定(m)个字符串,求在至少(k)个字符串中至少出现(2)次的不重叠的最长重复子串

    例题(11)+例题(6)
    (s)连起来得到(t)求出SA,二分答案(r),把(sa)分段,判断是否有某一段中包含了至少(k)个字符串部分的(suf)并且在这(k)个字符串部分的(max sa-min sage r)即可。
    时间复杂度(O((sum n)log(sum n)))

    (13.)给定(m)个字符串,求自己本身或者自己的反转在至少(k)个字符串中都出现的最长子串

    把每个串和自己的反转直接接起来然后跑例题(11)

    (14.)给定(s),求(s)的字典序第(k)小的子串

    找到满足(t=sumlimits_{j=1}^{i-1}n+1-sa_j-h_j)的最大的(i),那么这个子串就是(s_{sa_i,sa_i+h_i+k-t-1})
    可以通过预处理+查询时二分做到(O(n)-O(log n))

    自动机

    Link

  • 相关阅读:
    C++(函数默认参数)
    C++(引用六)
    C++(引用五)
    C++(引用四)
    C++(引用三)
    C++(引用二)
    C++(引用一)
    划水。。。
    2019.11.7
    lyc——2019.10.31
  • 原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/12171891.html
Copyright © 2011-2022 走看看